Creando tu librería JavaScript con requirejs y gulp

Si desarrollas aplicaciones web con alta carga de código en cliente, es posible que tengas que desarrollarte tus propias funciones JavaScript. Incluso tu propia librería o framework si es el caso.

Una opción es abrir tu editor de texto favorito, crear un archivo .js y empezar a teclear. Total, jQuery viene en un solo archivo .js, ¿verdad? Antes de hacer esto, párate a pensar un poco: ¿a qué nunca colocarías todo el código c# de tu proyecto en un solo fichero .cs? Tenerlo en varios ficheros permite localizar el código más rápidamente, tenerlo mejor organizado y evitar conflictos cuando se trabaja en equipo. Pues bien, eso mismo aplica a JavaScript.

Por supuesto los desarrolladores de jQuery (y de cualquier otra librería decente) no trabajan en un solo fichero. Tienen su código distribuido en ficheros separados que se combinan al final para crear la librería. Vamos a ver como podemos hacer esto de forma sencilla.

Lo primero que tenemos que hacer es precisamente definir los módulos de nuestra librería. Básicamente un módulo es un fichero .js, que define un conjunto de funciones que son accesibles a todos los clientes que usen el módulo. El código de dichos ficheros se organiza siguiendo un patrón que se conoce precisamente como patrón de módulo (revealing module pattern o alguna de sus variantes).

El problema está que en JavaScript no hay (de momento) ninguna manera de establecer las dependencias entre módulos: es decir, de indicar que el módulo A, depende del módulo B (lo que quiere decir que el módulo B debe estar cargad antes que el módulo A). Es ahí donde entra una librería llamada requirejs. Dicha librería permite especificar las dependencias entre módulos. Veamos un ejemplo de módulo con soporte para require:

  1. define([], function () {
  2.     var rnd = function () {
  3.         return 42;
  4.     };
  5.  
  6.     var isOdd = function (i) {
  7.         return (i % 2) != 0;
  8.     };
  9.  
  10.     return {
  11.         rnd: rnd,
  12.         isOdd: isOdd
  13.     };
  14.  
  15. });

Lo importante ahí es la funcion define. Dicha función está definida en requirejs y suele tomar dos parámetros:

  1. Un array con las dependencias del módulo.
  2. Una función con el código del módulo. Dicha función puede recibir como parámetro los módulos especificados en las dependencias.

El módulo define dos funciones (rnd y isOdd) y luego las exporta. Exportarlas significa que las hace visibles al resto de módulos que dependan de este. La forma de exportar es devolviendo un resultado: todo lo que se devuelve, es exportado.

Ahora vamos a declarar otro módulo (llamémosle main.js) que hará uso de este módulo que hemos definido:

  1. define([‘math/rnd’], function (math) {
  2.     var addOnlyIfOdd = function (a, b) {
  3.         var result = 0;
  4.         if (math.isOdd(a)) result += a;
  5.         if (math.isOdd(b)) result += b
  6.         return result;
  7.     };
  8.  
  9.     return {
  10.         addOnlyIfOdd: addOnlyIfOdd
  11.     };
  12. });

Es lo mismo que antes con la diferencia de que ahora el array de dependencias contiene el módulo (math/rnd). Este es nuestro módulo anterior. El nombre de un módulo es el nombre del fichero js tal cual (incluyendo la ruta) (existen los llamados módulos AMD donde esto no tiene porque ser así, pero tampoco es relevante ahora). Por lo tanto, en este código, el módulo anterior lo habíamos guardado en math/rnd.js Dentro de este módulo podemos usar todo lo que el módulo anterior exportaba a través del parámetro math.

¿Como usaríamos eso desde una página web?

Lo primero es cargar requirejs desde la página web y usar data-main para indicarle a require el módulo “inicial”:

  1. <!DOCTYPE html>
  2. <head>
  3.     <title>Demo</title>
  4.     <script data-main=«./main.js» src=«http://requirejs.org/docs/release/2.1.14/minified/require.js»></script>
  5. </head>
  6. <body>
  7. </body>

Si ejecutamos este fichero html y miramos la pestaña Network veremos como no solo se carga el fichero requirejs si no que tanto main.js como también math/rnd.js se cargan: require ha cargado tanto el módulo inicial como todas sus dependencias.

Esto nos permite dividir nuestro código JavaScript con la tranquilidad de que luego los scripts se cargarán en el orden correcto, gracias a requirejs.

Vale, pero estarás diciendo que jQuery viene en un solo js único y para cargar jQuery no debes usar require para nada. Y tienes razón. La realidad es que la gente de jQuery (y tantos otros frameworks) tienen su código dividido en módulos pero luego los unen todos en el orden correcto en un solo js. Y eso lo hacen cuando “construyen” jQuery. Vamos a ver un ejemplo usando gulp (en jQuery se usa grunt que es similar).

Gulp es un sistema de build basado en JavaScript. Se basa en node (así que es necesario tener node instalado). Pero teniendo node instalado, instalar gulp es seguir dos pasos:

  1. Ejecutar npm install –g gulp. Eso instala gulp a nivel “global”. Debe ejecutarse una sola vez.
  2. Ejecutar npm install –save-dev gulp. Eso debe ejecutarse desde el directorio donde tenemos el código fuente de nuestra librería. Eso instala gulp a nivel local y debe hacerse una vez por cada librería instalada.

El tercer paso es crear un fichero javascript, llamado gulpfile.js que será el que tenga las distintas tareas para generar nuestro proyecto JavaScript.

Para el ejemplo vamos a suponer que tenemos nuestro fichero main.js y el fichero math/rnd.js dentro de una subcarpeta src. El fichero gulpfile.js está en la carpeta que contiene a /src. Es decir tenemos una estructura tal que así:

image

Ahora vamos a crear una tarea en gulp para combinar todos los módulos de nuestro proyecto en un .js final. Para ello tenemos que instalar requirejs como módulo de node mediante npm install –save-dev requirejs (desde el directorio de nuestro proyecto).

Finalmente podemos crear una tarea, llamada “make” en nuestro gulpfile:

  1. var gulp = require(‘gulp’);
  2. var requirejs = require(‘requirejs’);
  3. gulp.task(‘make’, function () {
  4.     var config = {
  5.         baseUrl: ‘src’,
  6.         name: ‘main’,
  7.         out: ‘dist/demo.js’,
  8.         findNestedDependencies: true,
  9.         skipSemiColonInsertion: true,
  10.         optimize: ‘none’
  11.     };
  12.     requirejs.optimize(config, function (buildResponse) {
  13.         console.log(buildResponse);
  14.     });
  15. });

Cargamos los módulos de node que vamos a usar (gulp y require) y luego usamos requirejs.optimize para cargar el módulo incial, junto con todas sus dependencias y generar un solo .js. Esto es precisamente lo que hace la llamada a requirejs.optimize. El primer parámetro es la configuración. Existen muchas opciones de configuración, siendo las más importantes:

  1. baseUrl: Directorio base donde están los módulos
  2. name: Módulo inicial
  3. out: Fichero de salida
  4. optimize: Si debe minimificar el archivo de salida o no.

Una vez hemos modificado el fichero gulpfile.js ya podemos ejecutarlo, mediante gulp make. Esto invocará la tarea ‘make’ del fichero gulpfile:

image

Una vez ejecutado en el directorio dist tendremos un fichero (demo.js) con todos nuestros módulos combinados en el orden correcto. Este sería el fichero que distribuiríamos y que se usaría desde el navegador.

Eso nos permite tener nuestro código javascript bien separado y organizado, con la tranquilidad de que siempre podemos generar la versión unificada “final” usando gulp.

Por supuesto gulp sirve para mucho más que combinar los módulos. Puede minimificar salidas, pasar tests unitarios, realizar optimizaciones adicionales, tareas propias… en fin, cualquier cosa que uno esperaría de un sistema de builds.

¡Un saludo!

ASP.NET vNext–Model Binding

Bien, en el post anterior comentamos cuatro cosillas sobre el model binding en ASP.NET MVC y WebApi, sus semejanzas y sus diferencias. En ASP.NET vNext ambos frameworks se unifican así que es de esperar que el model binding también lo haga… Veamos como funciona el model binding de vNext.

Nota: Este post está realizado con la versión de ASP.NET vNext que viene con el VS14 CTP2. La mejor manera de probar dicha CTP es usando una VM en Azure creada a partir de una plantilla que ya la contiene instalada. Por supuesto todo lo dicho aquí puede contener cambios en la versión final 🙂

Pruebas de caja negra

Antes que nada he intentado hacer unas pruebas de “caja negra” para ver si el comportamiento era más parecido al de WebApi o al de MVC. He empezado con un proyecto web vNext vacío, y en el project.json he agregado la referencia a Microsoft.AspNet.Mvc. Luego me he creado un controlador como el siguiente:

  1. public class HomeController : Controller
  2. {
  3.     public IActionResult Index(Product product, Customer customer)
  4.     {
  5.         return View();
  6.     }
  7.  
  8.     public bool Post(Product product, Customer customer)
  9.     {
  10.         return true;
  11.     }
  12. }

Finalmente en el Startup.cs he configurado una tabla de rutas que combine MVC y WebApi:

  1. public class Startup
  2. {
  3.     public void Configure(IBuilder app)
  4.     {
  5.         app.UseServices(s => s.AddMvc());
  6.  
  7.         app.UseMvc(r =>
  8.         {
  9.             r.MapRoute(
  10.                 name: "default",
  11.                 template: "{controller}/{action}/{id?}",
  12.                 defaults: new { controller = "Home", action = "Index" });
  13.             r.MapRoute(
  14.                 name: "second",
  15.                 template: "api/{Controller}/{id?}"
  16.                 );
  17.         });
  18.     }
  19. }

Con esa tabla de rutas un POST a /Home/Index debe enrutarme por la primera acción del controlador (al igual que un GET). Mientras que un POST a /api/Home debe enrutarme por la segunda acción del controlador (mientras que un GET a /api/Home debe devolverme un 404). Para más información echa un vistazo a mi post sobre el routing en vNext.

Las clases Customer y Product contienen simplemente propiedades:

  1. public class Customer
  2. {
  3.     public int Id { get; set; }
  4.     public string Name { get; set; }
  5.     public string Gender { get; set; }
  6. }

  1. public class Product
  2. {
  3.     public int Id { get; set; }
  4.     public string Name { get; set; }
  5. }

Luego he usado cURL para realizar unos posts y ver que es lo que tenía:

curl –data "Id=1&Name=eiximenis&Gender=Male" http://localhost:49228/  –header "Content-type:application/x-www-form-urlencoded"

Con esto simulo un post a que contenga los datos Id, Name y Gender y eso es lo que recibo en el controlador (en el método Index):

image

Este comportamiento es el mismo que en ASP.NET MVC. Ahora cambio la petición de cURL para enviar la misma petición pero a /api/Home para que se me enrute al método Post (estilo WebApi). Mi idea era ver si para enrutamiento tipo MVC se usaba un binding parecido a MVC y para enrutamiento tipo WebApi (sin acción y basado en verbo HTTP) se usaba un binding parecido al de WebApi:

curl –data "Id=1&Name=eiximenis&Gender=Male" http://localhost:49228/api/Home  –header "Content-type:application/x-www-form-urlencoded"

El resultado es que se me llama al método Post del controlador pero recibo exactamente los mismos valores que antes. Recordad que en WebApi eso NO era así. Así a simple vista parece que se ha elegido el modelo de model binding de ASP.NET MVC antes que el de web api.

Otra prueba ha sido realizar un POST contra /api/Home/10 (el parámetro 10 se corresponde al route value id) y dado que estamos pasando el id por URL quitarlo del cuerpo de la petición:

curl –data "Name=eiximenis&Gender=Male" http://localhost:49228/api/Home/10  –header "Content-type:application/x-www-form-urlencoded"

El resultado es el mismo que en el caso anterior (y coincide con ASP.NET MVC donde el model binder ni se preocupa de donde vienen los datos).

Por lo tanto estas pruebas parecen sugerir que en vNext el model binding que se sigue es el de ASP.NET MVC.

Claro que cuando uno pruebas de caja negra debe tener presente el máximo número de opciones… Porque resulta que si hago algo parecido a:

curl –data "{‘Name’:’eiximenis’,’Gender’:’Male’}"http://localhost:49228/api/Home  –header "Content-type:application/json"

Entonces resulta que ambos parámetros son null. Parece ser que vNext no enlaza por defecto datos en JSON, solo en www-form-urlencoded. Además mandar datos en JSON hace que los parámetros no se enlacen. Aunque mande datos a través de la URL (p. ej. como route values) esos no se usan.

Por supuesto vNext soporta JSON, pero es que nos falta probar una cosilla…

Atributo [FromBody]

De momento en vNext existe el atributo [FromBody] (pero no existe el [FromUri]). Ni corto ni perezoso he aplicado el FromBody a uno de los parámetros del controlador:

  1. public bool Post(Product product, [FromBody] Customer customer)
  2. {
  3.     return true;
  4. }

Y he repetido la última petición (el POST a /api/Home/10). Y el resultado ha sido… un error:

System.InvalidOperationException: 415: Unsupported content type Microsoft.AspNet.Mvc.ModelBinding.ContentTypeHeaderValue

He modificado la petición cURL para usar JSON en lugar de form-urlencoded:

curl –data "{‘Name’:’eiximenis’,’Gender’:’Male’}" http://localhost:49228/api/Home/10  –header "Content-type:application/json"

Y el resultado ha sido muy interesante:

image

El parámetro customer se ha enlazado a partir de los datos en JSON del cuerpo (el Id está a 0 porque es un route value y no está en el cuerpo de la petición) pero el parámetro product está a null. Por lo tanto el uso de [FromBody] modifica el model binding a un modelo más parecido al de WebApi.

WebApi solo permite un solo parámetro enlazado desde el cuerpo de la petición. Mi duda ahora era si vNext tiene la misma restricción. Mirando el código fuente de la clase JsonInputFormatter intuía que sí… y efectivamente. Aunque a diferencia de WebApi no da error si no que tan solo enlaza el primer parámetro. Así si tengo el método:

  1. public bool Post([FromBody] Product product, [FromBody] Customer customer)

Y repito la llamada cURL anterior, los datos recibidos son:

image

El parámetro product (el primero) se ha enlazado a partir del cuerpo de la petición y el segundo vale null.

¿Y como funciona todo (más o menos)?

Recordad que ASP.NET vNext es open source y que nos podemos bajar libremente el código de su repositorio de GitHub. Con este vistazo al código he visto algunas cosillas.

El método interesante es el método GetActionArguments de la clase ReflectedActionInvoker. Dicho método es el encargado de obtener los argumentos de la acción (por tanto de todo el proceso de model binding). Dicho método hace lo siguiente:

  • Obtiene el BindingContext. El BindingContext es un objeto que tiene varias propiedades, entre ellas 3 que nos interesan:
    1. El InputFormatterProvider a usar
    2. El ModelBinder a usar
    3. Los Value providers a usar
  • Obtiene los parámetros de la acción. Cada parametro viene representado por un objeto ParameterDescriptor. Si el controlador acepta dos parámtetros (customer y product) existen dos objetos ParameterDescriptor, uno representando a cada parámetro de la acción. Dicha clase tiene una propiedad llamada BodyParameterInfo. Si el valor de dicha propiedad es null se usa un binding más tipo MVC (basado en value providers y model binders). Si el valor no es null se usa un binding más tipo WebApi (basado en InputFormatters).

Por defecto vNext viene con los siguientes Value Providers:

  1. Uno para query string (se crea siempre)
  2. Uno para form data (se crea solo si el content type es application/x-www-form-urlencoded
  3. Otro para route values (se crea siempre)

La clave está en el uso del atributo [FromBody] cuando tenemos un parámetro enlazado mediante este atributo entonces no se usan los value providers si no los InputFormatters. Pueden haber dado de alta varios InputFormatters pero solo se aplicará uno (basado en el content-type). Por defecto vNext incluye un solo InputFormatter para application/json.

Ahora bien… qué pasa si tengo un controlador como el siguiente:

  1. public IActionResult Index([FromBody] Customer customer, Product product)
  2. {
  3.     return View();
  4. }

Y hago la siguiente petición?

C:UsersetomasDesktopcurl>curl –data "{‘Name’:’eiximenis’,’Gender’:’Male’}" http://localhost:38820/Home/Index/100?Name=pepe –header "Content-type:application/json"

Pues el valor de los parámetros será como sigue:

image

Se puede ver como el parámetro enlazado con el [FromBody] se enlaza con los parámetros del cuerpo (en JSON) mientras que el parámetro enlazado sin [FromBody] se enlaza con el resto de parámetros (de la URL, routevalues y querystring). En vNext el [FromUri] no es necesario: si hay un [FromBody] el resto de elementos deben ser enlazados desde la URL. Si no hay [FromBody] los elementos serán enlazados desde cualquier parte de la request.

Bueno… en este post hemos visto un poco el funcionamiento de ASP.NET vNext en cuanto a model binding. El resumen es que estamos ante un modelo mixto del de ASP.NET MVC y WebApi.

En futuros posts veremos como podemos añadir InputFormatters y ValueProviders para configurar el sistema de model binding de vNext.

Saludos!

Model binding en ASP.NET MVC y WebApi

Una de las confusiones más habituales con la gente que viene de MVC y pasa a WebApi es que el funcionamiento del model binding (es decir rellenar los parámetros de los controladores a partir de los datos de la request) es distinto entre ambos frameworks. La confusión viene porque a primera vista todo parece que funcione igual pero realmente hay diferencias muy profundas entre ambos frameworks.

Veamos, pues, algunas de las diferencias que hay entre ambos frameworks

ASP.NET MVC Value Providers primero y Model Binders después

En ASP.NET MVC el proceso de model binding está compuesto de dos pasos:

  1. Primero existe una serie de clases, llamadas value providers que se encargan de leer la petición HTTP y colocar los datos en un sitio común (como un diccionario). Así no importa si un dato (p. ej. Id) está en querystring o en formdata). Simplemente, se obtendrá ese Id y se colocará en el ese sitio común. Hay varios value providers porque cada value provider puede analizar una parte de la petición. Así hay un value provider que analiza la querystring, un par que analizan formdata (uno si el content-type es application/x-www-form-urlencoded y otro si es application/json) y así sucesivamente.
  2. Una vez los datos de la petición están en este lugar común entra en acción el model binder. El model binder recoge los datos de ese sitio común y los utiliza para crear los objetos que son pasados como parámetros en la acción del controlador. Cada parámetro es enlazado por un model binder distinto dependiendo del tipo del parámetro (aunque por defecto casi todos los tipos se enlazan usando el DefaultModelBinder, nosotros podemos crear model binders propios y vincularlos a tipos de parámetros).

Supongamos dos clases como las siguientes:

  1. public class Customer
  2. {
  3.    public int Id { get; set; }
  4.    public string Name {  get; set; }
  5.    public string Address{ get; set; }
  6. }
  7.  
  8. public class Product
  9. {
  10.    public int Id { get; set; }
  11.    public string Name { get; set; }
  12. }

Y una vista que envíe los datos:

  1. <form>
  2.     <input type="text" name="Id" /><br />
  3.     <input type="text" name="Name" /><br />
  4.     <input type="text" name="Address" /><br />
  5.     <input type="submit" value="send"/>
  6. </form>

Ahora si tenemos un controlador que tiene una acción con dos parámetros (Customer y Product) ¿qué es lo que recibimos? Pues lo siguiente:

image

Es decir el valor de las propiedades Id y Name se ha enlazado a ambos parámetros. Es lógico: los value providers han recogido 3 valores de la petición resultado de enviar el formulario (Id, Name y Address) y los han colocado en el “sitio común”. Luego cuando el model binder del parámetro customer debe crear el objeto usa los valores de dicho sitio común para enlazar las propiedades… lo mismo que el model binder del parámetro product. De ahí que las propiedades que se llamen igual tengan el mismo valor.

Por supuesto esta posibilidad está contemplada en ASP.NET MVC, de forma que el model binder entiende de “prefijos”. Así si modifico uno de los campos de texto para que sea:

  1. <input type="text" name="customer.Name" />

Ahora el valor del campo de la petición llamado “customer.Name” se enlazará solo a la propiedad Name del parámetro customer (product.Name será null).

El modelo de value providers + model binders es muy versátil y potente.

WebApi – Básicamente MediaTypeFormatters (básicamente)

En WebApi la aproximación es radicalmente distinta. Primero hay una distinción clara, clarísima, sobre si el parámetro de la acción se enlaza desde el cuerpo de la petición o desde cualquier otro sitio (p. ej. querystring).

En MVC el model binder no sabe de donde vienen los datos que usa para enlazar los parámetros del controlador, ya que los saca siempre del “sitio común” (donde lo dejan los value providers). WebApi usa una orientación distinta pero la regla de oro fundamental es: El cuerpo de la petición puede ser leído una sola vez.

En efecto en WebApi el cuerpo de la petición HTTP es un stream forward-only, lo que significa que puede ser leído una única vez (en ASP.NET MVC el cuerpo está cacheado ya que distintos value providers pueden leerlo). Eso está hecho por temas de rendimiento.

Así en WebApi se usa un paradigma basado básicamente en el content-type. Aparecen unos entes nuevos (los media type formatters) encargados de leer el cuerpo de la petición. Pero SOLO UN media type formatter puede leer el cuerpo (recuerda: puede ser leído una sola vez). Y como sabe WebApi qué media type formatter procesa el cuerpo de la petición? Pues basándose en el content-type.

El media type formatter lee pues el cuerpo de la petición y rellena un objeto de .NET (del tipo indicado) en base a dichos datos. Efectivamente: el media type formatter lee de la petición y enlaza las propiedades. Eso implica una restricción importante: tan solo un parámetro de la acción puede ser obtenido a partir de los datos del cuerpo de una petición.

Veamos la diferencia: he creado un controlador WebApi con un método análogo al anterior que recibe (via POST) un Customer y un Product:

  1. public class DataController : ApiController
  2. {
  3.     public bool Post(Product product, Customer customer)
  4.     {
  5.         return true;
  6.     }
  7. }

Y he modificado la vista para que haga post del formulario a dicho controlador. Existe un media type formatter que se encarga de leer los datos cuando el content-type es application/x-www-form-urlencoded (de hecho, realmente hay dos pero tampoco es necesario entrar en más detalles). ¿Y cual es el resultado? Pues un error. Concretamente una System.InvalidOperationException con el mensaje: Can’t bind multiple parameters (‘product’ and ‘customer’) to the request’s content.

La razón es que tenemos dos parámetros a rellenar basándonos en el cuerpo de la petición. Pero dicho cuerpo puede ser leído una sola vez por un media type formatter. Y un media type formatter tan solo puede enlazar un objeto.

Es ahí donde entra el atributo FromUri. Dicho atributo se aplica a parámetros de la accion para indicar que esos parámetros se deben enlazar a partir de datos encontrados en la URL (querystring):

  1. public bool Post(Product product, [FromUri] Customer customer)
  2. {
  3.     return true;
  4. }

Si ahora ejecutamos de nuevo podemos ver que recibe el controlador son:

image

Podemos ver como customer no tiene datos (a pesar de haber un campo Address en el cuerpo de la petición) porque customer se enlaza a partir de los datos de la URL (querystring).

Por defecto WebApi usa siempre un media type formatter cuando el parámetro es un tipo complejo (una clase) a no ser que haya el atributo FromUri aplicado. Si el parámetro es un tipo simple (p. ej. int o un string) intenta enlazarlo a partir de la URL a no ser que haya aplicado el atributo contrario [FromBody]. Eso sí recuerda que solo un parámetro puede ser enlazado desde el cuerpo de la petición.

WebApi – ModelBinders y Value Providers

Cuando se enlaza un parámetro que no viene del cuerpo de la petición (es decir que viene de la URL) WebApi usa entonces el mismo modelo que MVC. Es decir primero los value providers recogen los datos de la petición (excepto el cuerpo) y los colocan en un sitio común y luego los model binders usan los datos de este sitio cómún para enlazar los parámetros).

P. ej. modifico el controlador de WebApi para que acepte GET y enlace ambos parámetros desde la URL:

  1. public bool Get([FromUri]Product product, [FromUri] Customer customer)
  2. {
  3.     return true;
  4. }

Ahora si realizo una llamada GET a /api/Data/10?name=test lo que obtengo es:

image

Los value providers recogen los datos de la URL (hay dos, uno para los route values (como /10) y otro para la querystring) y los dejan en el sitio común. Luego los model binders usan esos datos para enlazar tanto product como customer y es por eso que obtengo los datos duplicados (es el caso análogo al caso inicial de ASP.NET MVC).

En resumen: hemos visto cuatro pinceladas de como ASP.NET MVC y WebApi enlazan los parámetros a los controladores haciendo especial énfasis en las diferencias. Dejamos para un post posterior el ver como funciona este tema en vNext pues recuerda que en vNext WebApi y MVC son un solo framework 🙂

Saludos!

Cifrado en WebApi

Muy buenas! El otro día recibí el siguiente correo (a través del formulario de contacto del blog):

Estoy desarrollando una serie de web apis para transacciones con tarjetas de crédito. Mi problema es que no encuentro la forma de cifrar los datos sensibles como el numero de tarjeta de crédito. con un web service esta claro como hacerlos pero no encuentro la forma en una web api. que me recomendas?

Es un tema interesante y que da para mucho pero a ver si podemos dar cuatro pinceladas

Antes de nada lo dicho en este post trata de evitar que alguien que use un sniffer para analizar el tráfico de red pueda ver los datos confidenciales que enviamos. Este post no pretende ni puede ser una solución completa al problema, ya que hay muchas casuísticas que se deben tratar y muchos tipos de ataque a los que debemos responder. Honestamente la solución más sencilla pasa por usar HTTPS. Si usas HTTPS delegas toda la seguridad en el canal. Sin duda es lo más sencillo. No hay que hacer nada especial para poder usar HTTPS en WebApi. Si usas IIS Express para desarrollar (lo habitual con VS) habilitar el soporte para HTTPS está a un click de distancia:

SNAGHTML5d216779

Al seleccionar SSL Enabled VS te indica tanto la URL http como la https (con otro puerto claro). Para que todo funcione IIS Express genera un certificado SSL de desarrollo y ya tienes HTTPS habilitado. Por supuesto cuando despliegues en producción deberás desplegar el certificado SSL de producción en IIS.

Conceptos básicos de cifrado

Si no quieres usar IIS entonces debes encriptar los datos en el cliente y desencriptarlos en el servidor. Eso indica que debes especificar que método de encriptación se usa… hay muchos pero a grandes rasgos se dividen en dos grandes grupos:

  1. De clave simétrica
  2. De clave asimétrica

En los métodos de clave simétrica, cliente y servidor comparten una clave. Dicha clave es usada para generar texto cifrado a partir de texto plano… y viceversa. Es decir alguien que conozca la clave puede tanto enviar mensajes cifrados y descifrarlos.

En los métodos de clave asimétrica cada uno de los actores tiene un par de claves. Ambas claves sirven tanto para cifrar como para descifrar, pero con una particularidad: lo cifrado con una clave debe ser descifrado con la otra y viceversa.

Los métodos asimétricos tienen a priori una seguridad mayor que el método simétrico. Imagina a alguien llamado Alice que quiera enviar un mensaje a Bob. Con un método simétrico:

  1. Alice cifraría el mensaje con la clave K de encriptación
  2. Bob usaría la misma clave K para descifrarlo
  3. Si Bob quiere enviar una respuesta cifrada a Alice la cifraría usando la misma clave K y Alice lo descifraría usando K.

El punto débil aquí es el intercambio de claves. Si queremos que un mensaje de Alice a Bob solo pueda ser leído por Bob debemos asegurarnos que la clave K solo sea conocida por Alice y por Bob. Porque si dicha clave K llega a un tercero este podrá leer el mensaje.

Para evitar este punto entran los sistemas asimétricos:

  1. Bob tiene su par de claves Kpu y Kpr. Bob publica la clave Kpu en su web pero se guarda bajo llave la clave Kpr.
  2. Alice quiere mandarle un mensaje cifrado a Bob. Para ello lo cifra con la clave Kpu de Bob (que está en su web).
  3. Bob recibe el mensaje y lo descifra usando su propia clave Kpr (que tiene guardada bajo llave).
  4. Si Bob quiere responder a Alice puede cifrar su respuesta usando la clave Kpu de Alice (que también está en la web de Alice). Alice puede descifrar el texto usando su propia clave Kpr.

No hay intercambio de claves. Par enviar algo a Bob se cifra con su clave pública (la Kpu de Bob) y tan solo Bob lo puede descifrar con su Kpr (su clave privada). Los métodos asimétricos son más lentos (tanto para cifrar como para descifrar) que los asimétricos y tienen sus propios problemas: existen ataques sobre la Kpu para intentar averiguar la Kpr asociada, ya que es obvio que tienen alguna relación. Algunos algoritmos asimétricos han sido rotos gracias a que se han encontrado relaciones más o menos obvias entre ambas claves que permiten deducir (o acotar suficientemente el ámbito de búsqueda para permitir un ataque por fuerza bruta) la clave privada al saber la pública.

Cifrando y descifrando datos

Bueno… veamos como podemos implementar un cifrado asimétrico usando WebApi. Nos centramos solo en el cifrado de datos (no entraremos en temas de firma digital aunque los principios sean muy parecidos). El algoritmo que usaremos será RSA (pero vamos, hay otros) a través de la clase RSACryptoServerProvider.

La propia clase se encarga de crear el par de claves necesario y luego pueden usarse los métodos ToXmlString() y FromXmlString() para guardar dichas claves o bien incorporar dichas claves en un RSACryptoServerProvider:

  1. static void Main(string[] args)
  2. {
  3.     var rsa = new RSACryptoServiceProvider();
  4.     var both = rsa.ToXmlString(true);
  5.     var pub = rsa.ToXmlString(false);
  6.     File.WriteAllText("both.xml", both);
  7.     File.WriteAllText("pub.xml", pub);
  8. }

Dicho código crea dos archivos xml (both.xml y pub.xml). El primero contiene el par de claves (pública y privada) mientras que el segundo contiene tan solo la clase pública. Por supuesto hay mejores maneras de guardar las claves pero para este post eso será suficiente.

Ahora vamos a crear una aplicación ASP.NET WebApi.

He generado un par de claves y las he guardado en una clase (he copiado el contenido entero de both.xml en una propiedad CryptoKeys.Both y lo mismo para CryptoKeys.Pub):

  1. class CryptoKeys
  2. {
  3.     internal const string Both = "[CONTENIDO ENTERO DEL FICHERO BOTH.XML]";
  4.     internal const string Pub = "[CONTENIDO ENTERO DEL FICHERO PUB.XML]";
  5. }

Vale… en un mundo real esto NO estaría hardcodeado pero… ¡estamos en el mundo ideal de los blogs!

Ahora creamos un controlador WebApi para que nos devuelva la clave pública:

  1. public class KeyController : ApiController
  2. {
  3.     // GET api/values
  4.     public string Get()
  5.     {
  6.         return CryptoKeys.Pub;
  7.     }
  8. }

Una llamada GET a /api/Key nos permite obtener la clave pública del servidor:

SNAGHTML5d500782

Vale… vamos a ver el código del cliente.

  1. static void Main(string[] args)
  2. {
  3.     DoEverythingAsync().Wait();
  4. }
  5.  
  6. private static async Task DoEverythingAsync()
  7. {
  8.     string kpu = null;
  9.     // Obtenemos clave del servidor
  10.     using (var client = new HttpClient())
  11.     {
  12.         client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
  13.         var data = await client.GetAsync("http://localhost:25986/api/key");
  14.         kpu = await data.Content.ReadAsStringAsync();
  15.         kpu = JsonConvert.DeserializeObject<string>(kpu);
  16.     }
  17.  
  18.     var encrypted = EncryptData(kpu, "PRIVATE DATA TO SEND");
  19.     await SendEncryptedDataAsync(encrypted);
  20. }

Básicamente obtenemos la clave pública (llamando a /api/Key del servidor), encriptamos unos datos y los enviamos. El código de EncryptData es:

  1. private static byte[] EncryptData(string kpu, string data)
  2. {
  3.     var rsa = new RSACryptoServiceProvider();
  4.     rsa.FromXmlString(kpu);
  5.     var bytes = new byte[data.Length * sizeof(char)];
  6.     Buffer.BlockCopy(data.ToCharArray(), 0, bytes, 0, bytes.Length);
  7.     var encrypted = rsa.Encrypt(bytes, true);
  8.     return encrypted;
  9. }

Simplemente usamos el método FromXmlString para inicializar el RSACryptoServiceProvider con la clave pública obtenida previamente. Luego llamamos a Encrypt para encriptar los datos.

Finalmente el código de SendEncryptedDataAsync:

  1. private static async Task SendEncryptedDataAsync(byte[] encrypted)
  2. {
  3.     using (var client = new HttpClient())
  4.     {
  5.         var content = new FormUrlEncodedContent(new[]
  6.         {
  7.             new KeyValuePair<string, string>("CC", Convert.ToBase64String(encrypted)),
  8.             new KeyValuePair<string, string>("Name", "edu"),
  9.         });
  10.         var result = await client.PostAsync("http://localhost:25986/api/Test", content);
  11.         Console.WriteLine("Status Code: " + result.StatusCode);
  12.     }
  13. }

Enviamos dos campos en el POST, uno llamado CC (el encriptado) y otro llamado Name que NO está encriptado.

Hecho esto, en el servidor nos creamos un controlador de prueba (Test):

  1. public class TestController : ApiController
  2. {
  3.     public void Post(CreditCardInfo card)
  4.     {
  5.         
  6.     }
  7. }
  8.  
  9. public class CreditCardInfo
  10. {
  11.     public string Name { get; set; }
  12.     public string CC { get; set; }
  13. }

Si ahora levantamos la aplicación WebApi y ejecutamos nuestra aplicación de consola cliente vemos que los datos llegan al servidor:

image

Por supuesto el servidor tiene que descifrarlos… Veamos como:

  1. public void Post(CreditCardInfo card)
  2. {
  3.     var rsa = new RSACryptoServiceProvider();
  4.     rsa.FromXmlString(CryptoKeys.Both);
  5.     var bytes = Convert.FromBase64String(card.CC);
  6.     var decrypted = rsa.Decrypt(bytes, true);
  7.     var chars = new char[decrypted.Length / sizeof(char)];
  8.     Buffer.BlockCopy(decrypted, 0, chars, 0, decrypted.Length);
  9.     var decryptedString = new string(chars);
  10. }

¡Al final en la variable decryptedString estarán los datos descifrados!

Un poco más de integración con WebApi

Tener que colocar el código de descifrado continuamente no es muy agradecido. Por suerte podemos integrarnos dentro de WebApi de forma relativamente simple.

WebApi usa media type formatters para pasar los datos que vienen en el cuerpo de la petición http a un parámetro del controlador. En nuestro caso mandamos dos datos (Name y CC). Para saber que media type formatter usar WebApi debe saber en qué formato están los datos en la petición y para ello usa la cabecera content-type. El valor normal de content-type cuando enviamos datos vía POST es application/x-www-form-urlencoded (si enviasemos un JSON usaríamos application/json). WebApi viene con soporte nativo para application/x-www-form-urlencoded a través de dos media type formatters:

  • FormUrlEncodedMediaTypeFormatter: Se usa si el controlador recibe un JToken o un FormCollection
  • JQueryMvcFormUrlEncodedFormatter: Se usa si el controlador recibe un objeto como parámetro. Deriva del anterior.

En nuestro caso el controlador recibe un CreditCardInfo por lo que será JQueryMvcFormUrlEncodedFormatter el que procese los datos de la petición, y cree el CreditCardInfo que recibe el controlador en el método POST.

Así la solución pasa por derivar de JQueryMvcFormUrlEncodedFormatter y añadir en la clase derivada el código de descifrado. Veamos como hacerlo de forma sencilla. Primero creamos la clase CypheredFomUrlEncodedMediaTypeFormatter:

  1. public class CypheredFormUrlEncodedMediaTypeFormatter : JQueryMvcFormUrlEncodedFormatter
  2. {
  3.     public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content,
  4.         IFormatterLogger formatterLogger)
  5.     {
  6.         return this.ReadFromStreamAsyncCore(type, readStream, content, formatterLogger);
  7.     }
  8. }

Sobreescribimos el método ReadFromStreamAsync. Este método debe leer los datos del cuerpo de la petición, crear el objeto del tipo type indicado y pasar los datos de la petición al objeto creado.

Todo el código lo tendremos en el método ReadFromStreamAsyncCore de la propia clase:

  1. public async Task<object> ReadFromStreamAsyncCore(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
  2.     {
  3.         var obj =
  4.             await base.ReadFromStreamAsync(typeof(FormDataCollection), readStream, content, formatterLogger) as
  5.                 FormDataCollection;
  6.         var binded = obj.ReadAs(type);
  7.         foreach (var property in type.GetProperties().Where(p => p.PropertyType == typeof(string)))
  8.         {
  9.             var attr = property.GetCustomAttribute<CypheredDataAttribute>();
  10.             if (attr != null)
  11.             {
  12.                 property.SetValue(binded, Decrypt(property.GetValue(binded) as string));
  13.             }
  14.         }
  15.  
  16.         return binded;
  17.     }

El método ReadFromStreamAsyncCore hace lo siguiente:

  1. Usamos el método (de la clase base) ReadFromStreamAsync pasándole como parámetro un FormDataCollection. Con eso obtenemos un FormDataCollection (básicamente un diccionario clave, valor) con los datos del cuerpo de la petición. Eso nos funciona porque derivamos de FormUrlEncodedMediaTypeFormatter que tiene soporte para FormDataCollection.
  2. Usamos el método (de extensión) ReadAs de FormDataCollection que crea un objeto del tipo indicado a partir de los datos de un FormDataCollection. Es decir, hace el binding de propiedades.
  3. Ahora iteramos sobre todas las propiedades de tipo cadena y miramos si alguna tiene el atributo CypheredDataAttribute aplicado.
    1. Si lo tiene desciframos el valor de dicha propiedad mediante una llamada a Decrypt.

El código del método Decrypt es básicamente el mismo que teníamos antes en el controlador:

  1. private string Decrypt(string encryptedBase64)
  2. {
  3.     var rsa = new RSACryptoServiceProvider();
  4.     rsa.FromXmlString(CryptoKeys.Both);
  5.     var bytes = Convert.FromBase64String(encryptedBase64);
  6.     var decrypted = rsa.Decrypt(bytes, true);
  7.     var chars = new char[decrypted.Length / sizeof(char)];
  8.     Buffer.BlockCopy(decrypted, 0, chars, 0, decrypted.Length);
  9.     var decryptedString = new string(chars);
  10.     return decryptedString;
  11. }

Con esto ya tenemos un media type formatter que descifrará automáticamente todas aquellas propiedades string decoradas con [CypheredData]. El código de CypheredDataAttribute es trivial:

  1. [AttributeUsage(AttributeTargets.Property)]
  2. public class CypheredDataAttribute : Attribute
  3. {
  4. }

Ahora nuestra clase CreditCardInfo nos queda de la siguiente manera:

  1. public class CreditCardInfo
  2. {
  3.     public string Name { get; set; }
  4.     [CypheredData]
  5.     public string CC { get; set; }
  6. }

¡Un último detalle! Tenemos que agregar nuestro MediaTypeFormatter en la configuración de webapi:

  1. config.Formatters.Remove(
  2.     config.Formatters.Single(f => typeof (JQueryMvcFormUrlEncodedFormatter) == f.GetType()));
  3. config.Formatters.Add(new CypheredFormUrlEncodedMediaTypeFormatter());

Y lo mejor: ¡en el controlador no tenemos que hacer nada para tener los datos descifrados!

image

¡Listos! Hemos visto como podemos enviar datos cifrados y como podemos integrarnos un poco dentro de webapi para que el descifrado sea sencillo. No hemos cubierto todos los casos posibles, pero espero que os haya dado algunas ideas de como implementarlo!

Saludos!