Hace algunos días Juan Quijano escribió un post en GenBetaDev con este mismo título donde comentaba lo poco que le gustaba que la funciones devolviesen null y lo que hacía para evitar errores en ese caso.

Este post es mi respuesta a su post, ya que personalmente no me gusta la solución que presenta. En general termina con una solución como la siguiente:

  1. public class Modelo
  2. {
  3.     public Persona GetPersonaByName(string nombre)
  4.     {
  5.         Persona persona = new Persona();
  6.         if (nombre == "pepe")
  7.         { persona = new Persona { Nombre = nombre, Edad = 14 }; }
  8.         return persona;
  9.     }
  10. }
  11. public class Persona
  12. {
  13.     public string Nombre { get; set; }
  14.     public int Edad { get; set; }
  15.     public Persona()
  16.     {
  17.         Nombre = string.Empty;
  18.         Edad = 0;
  19.     }
  20. }

No me gusta por varias razones, la principal es que “oculta” la causa, devolviendo una Persona “sin datos” cuando no se encuentra la persona. Está estableciendo una convención que nadie más sabe: que una persona con el nombre vacío “no existe” en realidad. El código va a terminar llenándose de ifs para validar si el nombre es o no vacío para hacer algo o no. A diferencia de un null, donde olvidarte del if genera un error en ejecución (y por lo tanto es visible), dejarte un if en este caso hará que tu código se comporte mal… y a veces esto puede ser mucho, pero que mucho, más difícil que detectar el null reference, que al menos viene con stack trace. Otro problema es que no siempre existe en todo el rango de valores posibles un valor que pueda ser usado como “indicador de que no hay datos”.

Hay varios patrones para tratar esos caso, el más conocido el NullObject. De hecho un NullObject “mal hecho” es lo que propone Juan en su post. No soy muy amante del NullObject, aunque lo he usado a veces (la última hace poco en un refactoring, donde se tuvo que “desactivar” toda una funcionalidad. En este caso lo hicimos creando un NullObject del objeto que se estaba usando, de forma que el impacto en el resto del código (unos 90 proyectos de VS) fue nulo).

No quiero hablar del NullObject, si no presentar otra alternativa. En este caso una clase que contenga el valor más un indicador de si el valor existe o no. Vamos lo equivalente a Nullable<T> pero para cualquier tipo (sí… incluso los que pueden ser null). A priori parece que no ganamos nada pero dejadme un rato y veréis las ventajas que aporta.

La versión inicial de nuestra clase sería:

  1. public struct Maybe<T>
  2. {
  3.  
  4.     private readonly T _value;
  5.     private readonly bool _isEmpty;
  6.     private readonly bool _initialized;
  7.  
  8.     public T Value
  9.     {
  10.         get { return _value; }
  11.     }
  12.  
  13.     public bool IsEmpty
  14.     {
  15.         get { return (!_initialized) || _isEmpty; }
  16.     }
  17.  
  18.     public Maybe(T value)
  19.     {
  20.         _value = value;
  21.         _isEmpty = ((object)value) == null;
  22.         _initialized = true;
  23.     }
  24.  
  25.     public static Maybe<T> Empty()
  26.     {
  27.         return new Maybe<T>();
  28.     }
  29. }

Un punto importante es que no es una clase, es una estructura. Eso es para evitar que alguien que declare que devuelve un Maybe<T> termine devolviendo un null (recordad que queremos evitar los null).

Vale, esta estrcutura, tal cual está no nos aporta casi nada útil. El método del Modelo que presentaba Juan quedaría ahora como:

  1. public Maybe<Persona> GetPersonaByName(string nombre)
  2. {
  3.     Persona persona = new Persona();
  4.     if (nombre == "pepe")
  5.     {
  6.         persona = new Persona { Nombre = nombre, Edad = 14 };
  7.         return new Maybe<Persona>(persona);
  8.     }
  9.     return Maybe<Persona>.Empty();
  10. }

O devolvemos un Maybe relleno con la persona o devolvemos un Maybe vacío.  El test que usaba Juan quedaría como sigue:

  1. [TestMethod]
  2. public void GetPersonaByName_con_null_devuelve_string_empty()
  3. {
  4.     var modelo = new Modelo();
  5.     var persona = modelo.GetPersonaByName(null);
  6.     Assert.AreEqual(string.Empty, persona.Value.Nombre);
  7. }

Este test falla y la razón es obvia: persona.Value es null por lo que persona.Value.Nombre da un NullReferenceException. Podría añadir un if en el código para validar si person.IsEmpty es true, y en este caso no hacer nada. Personalmente prefiero mil veces un if (person.IsEmpty) que un if (person.Nombre ==””) ya que el primer if deja mucho claro que se pretende. Pero está claro, que no hemos ganado mucho. Como digo, dicha estructura apenas aporta nada.

Lo bueno es preparar dicha estructura para que pueda ser usada como un monad. Lo siento, soy incapaz de encontrar palabras sencillas para definir que es un monad porque el concepto es muy profundo, así que os dejo con el enlace de la wikipedia: http://en.wikipedia.org/wiki/Monad_(functional_programming)

Ahora vamos a preparar nuestra estructura para que pueda ser usada como un monad:

  1. public struct Maybe<T>
  2. {
  3.  
  4.     private readonly T _value;
  5.     private readonly bool _isEmpty;
  6.     private readonly bool _initialized;
  7.  
  8.     public T Value
  9.     {
  10.         get { return _value; }
  11.     }
  12.  
  13.     public bool IsEmpty
  14.     {
  15.         get { return (!_initialized) || _isEmpty; }
  16.     }
  17.  
  18.     public Maybe(T value)
  19.     {
  20.         _value = value;
  21.         _isEmpty = ((object)value) == null;
  22.         _initialized = true;
  23.     }
  24.  
  25.     public static Maybe<T> Empty()
  26.     {
  27.         return new Maybe<T>();
  28.     }
  29.  
  30.     public void Do(Action<T> action)
  31.     {
  32.         if (!IsEmpty) action(Value);
  33.     }
  34.  
  35.     public void Do(Action<T> action, Action elseAction)
  36.     {
  37.         if (IsEmpty)
  38.         {
  39.             action(Value);
  40.         }
  41.         else
  42.         {
  43.             elseAction();
  44.         }
  45.     }
  46.  
  47.     public TR Do<TR>(Func<T, TR> action)
  48.     {
  49.         return Do(action, default(TR));
  50.     }
  51.  
  52.     public TR Do<TR>(Func<T, TR> action, TR defaultValue)
  53.     {
  54.         return IsEmpty ? defaultValue : action(Value);
  55.     }
  56.  
  57.  
  58.     public Maybe<TR> Apply<TR>(Func<T, TR> action)
  59.     {
  60.         return IsEmpty ? Maybe<TR>.Empty() : new Maybe<TR>(action(Value));
  61.     }
  62. }

He añadido dos familias de métodos:

  1. Método Do para hacer algo solo si Maybe tiene valor
  2. Método Apply para encadenar Maybes. Este es el más potente y lo veremos luego.

Empecemos por los métodos Do. Dichos métodos básicamente nos permiten evitar el if(). Son poco más que una pequeña ayuda que nos proporciona la estructura. Mi test quedaría de la siguiente manera:

  1. [TestMethod]
  2. public void GetPersonaByName_con_null_devuelve_string_empty()
  3. {
  4.     var modelo = new Modelo();
  5.     var persona = modelo.GetPersonaByName(null);
  6.     var name = string.Empty;
  7.     persona.Do(p => name = p.Nombre);
  8.     Assert.AreEqual(string.Empty, name);
  9. }

El código del Do se ejecuta solo si hay valor, es decir si se ha devuelto una persona.

Podríamos reescribir el test usando otra de las variantes de Do:

  1. [TestMethod]
  2. public void GetPersonaByName_con_null_devuelve_string_empty()
  3. {
  4.     var modelo = new Modelo();
  5.     var persona = modelo.GetPersonaByName(null);
  6.     var name = persona.Do(p => p.Nombre, "no_name");
  7.     Assert.AreEqual("no_name", name );
  8. }

No hay mucho más que decir sobre los métodos Do… porque el método más interesante es Apply ;)

El método Apply me permite encadenar Maybes. Para ver su potencial, cambiaré el método del Modelo:

  1. public Maybe<Persona> GetPersonaByName(string nombre)
  2. {
  3.     Persona persona = new Persona();
  4.     if (nombre == "pepe")
  5.     {
  6.         persona = new Persona {Nombre = null, Edad = 42};
  7.         return new Maybe<Persona>(persona);
  8.     }
  9.     return Maybe<Persona>.Empty();
  10. }

Ahora si le paso “pepe” me da a devolver una Persona pero con el Nombre a null. Tratar esos casos con ifs se vuelve muy complejo y costoso. Apply viene en nuestra ayuda:

  1. [TestMethod]
  2. public void Acceder_a_nombre_null_no_da_probleamas()
  3. {
  4.     var modelo = new Modelo();
  5.     var persona = modelo.GetPersonaByName("pepe");
  6.     // En este punto tenemos un Maybe relleno pero value.Nombre es null
  7.     var nombreToUpper = string.Empty;
  8.     nombreToUpper = persona.Apply(p => p.Nombre).Do(s =>s.ToUpper(), "NO_NAME");
  9.     Assert.AreEqual("NO_NAME", nombreToUpper);
  10. }

La variable persona es un Maybe<Persona> con un valor. El método Apply lo que hace es básicamente ejecutar una transformación sobre el valor (el objeto Persona) y devolver un Maybe con el resultado. En este caso transformamos el objeto persona a p.Nombre, por lo que el valor devuelto por Apply es un Maybe<string>. Y como el valor de p.Nombre era null, el Maybe está vacío.

La combinación de Apply y Do permite tratar con valores nulos de forma muy sencilla y elegante.

Si os pregunto que capacidades funcionales tiene C# seguro que muchos responderéis LINQ… Porque no hacemos que nuestra clase Maybe<T> pueda participar del juego de LINQ? Por suerte eso es muy sencillo. Para ello basta con que Maybe<T> implemente IEnumerable<T> añadiendo esas dos funciones:

  1. public IEnumerator<T> GetEnumerator()
  2. {
  3.     if (IsEmpty) yield break;
  4.     yield return _value;
  5. }
  6.  
  7. IEnumerator IEnumerable.GetEnumerator()
  8. {
  9.     return GetEnumerator();
  10. }

Básicamente un Maybe<T> lleno se comporta como una colección de un elemento de tipo T, mientras que un Maybe<T> vacío se comporta como una colección vacía. A partir de aquí… tenemos todo el poder de LINQ para realizar transformaciones, consultas, uniones, etc… con nuestros Maybe<T> con otros Maybe<T> o cualquier otra colección. P. ej. podríamos tener el siguiente código:

  1. [TestMethod]
  2. public void Comprobar_Que_Nombre_es_Null()
  3. {
  4.     var modelo = new Modelo();
  5.     var persona = modelo.GetPersonaByName("pepe");
  6.     var tiene_nombre= persona.Apply(p => p.Nombre).Any();
  7.     Assert.IsFalse(tiene_nombre);
  8. }

Y por supuesto podemos iterar con foreach sobre los elementos de un Maybe<T> :)

Ya para finalizar vamos a añadir un poco más de infrastructura a la clase Maybe<T>. En concreto soporte para la comparación:

  1. public static bool operator ==(Maybe<T> one, Maybe<T> two)
  2. {
  3.     if (one.IsEmpty && two.IsEmpty) return true;
  4.     return typeof(T).IsValueType ?
  5.         EqualityComparer<T>.Default.Equals(one._value, two._value) :
  6.     object.ReferenceEquals(one.Value, two.Value);
  7. }
  8.  
  9. public bool Equals(Maybe<T> other)
  10. {
  11.     return _isEmpty.Equals(other._isEmpty) && EqualityComparer<T>.Default.Equals(_value, other._value);
  12. }
  13.  
  14. public override bool Equals(object obj)
  15. {
  16.     if (ReferenceEquals(null, obj)) return false;
  17.     return obj is Maybe<T> && Equals((Maybe<T>)obj);
  18. }
  19.  
  20. public static bool operator !=(Maybe<T> one, Maybe<T> two)
  21. {
  22.     return !(one == two);
  23. }
  24.  
  25. public override int GetHashCode()
  26. {
  27.     unchecked
  28.     {
  29.         return (_isEmpty.GetHashCode() * 397) ^ EqualityComparer<T>.Default.GetHashCode(_value);
  30.     }
  31. }

Maybe<T> intenta replicar el comportamiento de comparación de T. Es decir:

  • Dos Maybe<T> son “Equals” si los dos Ts de cada Maybe son “Equals”
  • Un Maybe<T> == otro Maybe<T> si:
    • Ambos Ts son el mismo objeto (en el caso de tipo por referencia)
    • Ambos Ts son “Equals” en el caso de tipos por valor

P. ej. el siguiente test valida el comportamiento de ==:

  1. var i = 10;
  2. var i2 = 10;
  3. var p = new Persona();
  4. var p2 = new Persona();
  5. Assert.IsTrue(new Maybe<int>(i) == new Maybe<int>(i2));
  6. Assert.IsFalse(new Maybe<Persona>(p) == new Maybe<Persona>(p2));

Y finalmente añadimos soporte para la conversión implícita de Maybe<T> a T:

  1. public static implicit operator Maybe<T>(T from)
  2. {
  3.     return new Maybe<T>(from);
  4. }

Dicha conversión nos permite simplificar las funciones que deben devolver un Maybe<T>. Así ahora la función del modelo puede ser:

  1. public Maybe<Persona> GetPersonaByName(string nombre)
  2. {
  3.     return nombre == "pepe" ?
  4.         new Persona {Nombre = null, Edad = 42} :
  5.         null;
  6. }

Fíjate que la función GetPersonaByName sigue devolviendo un Maybe<Persona> pero para el código es como si devolviese un Persona. El return null se traduce a devolver un Maybe<Persona> vacío.

Bueno… con eso termino el post. Espero que os haya resultado interesante y que hayáis visto otras posibles maneras de lidiar con las dichosas referencias null.

Saludos!

con no comments
Archivado en:

Una de las características de ASP.NET vNext son los “comandos K”, es decir aquellos comandos que se invocan desde línea de comandos a través del fichero K.cmd.

Dichos comandos están definidos en el project.json y la idea es que ofrezcan tareas necesarias durante el ciclo de vida de compilación y pruebas. Así podemos tener un comando (p. ej. K run) que nos ejecute el proyecto y otro (K test) que nos lance los tests unitarios. Recordad siempre que ASP.NET vNext se crea con el objetivo de que sea multiplataforma total: no solo que sea ejecutable en Linux o MacOSX a través de Mono, si no que sea posible desarrollar en esos sistemas operativos. Y eso implica “desligarse” de Visual Studio. Por supuesto, eso no quita que VS añada e implemente su propio soporte para el ciclo de vida de compilación y pruebas.

De hecho, incluso actualmente en VS no es nuevo usar comandos para gestionar algunas de las tareas necesarias para el ciclo de vida. Así muchos de vosotros conoceréis el comando Install-Package para instalar un paquete NuGet. Así, a dia de hoy, usamos la “Package Manager Console” que no es más que un wrapper sobre PowerShell. Para algunos comandos (como el citado Install-Package) el propio VS ofrece una alternativa gráfica pero hay otros comandos que solo se pueden lanzar desde dicha consola. El ejemplo más claro son todos los comandos de EF Migrations (p. ej. Update-Database).

La razón de que ASP.NET vNext se “olvide” de los comandos Powershell es que Powershell solo funciona en Windows y recuerda… ASP.NET vNext es multiplataforma de verdad.

Por supuesto nosotros podemos crear nuestros propios “comandos K” para añadir tareas que nos sean necesarias o útiles para el ciclo de vida de compilación y pruebas de la aplicación. Porque básicamente un comando definido en el project.json lo único que hace es invocar a un ensamblado.

Vamos a ver como podemos hacerlo. :)

Los comandos residen en un assembly que debe ser compilado con vNext. En nuestro caso vamos a crear una “ASP.NET vNext Console Application” usando VS14 CTP3. En mi caso la he llamado TestCommands.

image

La verdad es que el template que viene con VS14 CTP3 para dicho tipo de aplicaciones es casi inútil, pero bueno… menos da una piedra. Dicho template genera la clásica clase “Program” con su método Main, útil para ejecutables, pero en nuestro caso nuestra aplicación estará lanzada a través de un comando K, así que será el propio framework de ASP.NET vNext quien invocará nuestra aplicación.

Lo primero es agregar una dependencia a Microsoft.Framework.Runtime.Common en nuestro project.json:

  1. "dependencies": {
  2.     "Microsoft.Framework.Runtime.Common": "1.0.0-alpha3"
  3. },

Luego podemos modificar la clase Program para que quede de la siguiente así:

  1. public class Program
  2. {
  3.     public Program(IApplicationEnvironment env)
  4.     {
  5.         Console.WriteLine("In Progrm.ctor " + env.ApplicationBasePath);
  6.     }
  7.     public void Main(string[] args)
  8.     {
  9.         Console.WriteLine("In main");
  10.         Console.ReadLine();
  11.     }
  12. }

Si lo ejecutas con VS directamente verás algo parecido:

image

Por supuesto puedes ejecutarlo también usando KRE. Para ello asegúrate de tener en el path la carpeta donde está KRE instalado. Puedes tener varios KREs instalados side by side y por defecto se instalan en %HOME%\.kre\packages. Así p. ej. en mi maquina tengo:

image

Debes agregar al path la carpeta bin del KRE que quieras usar. Así p. ej. yo tengo agregado al path la carpeta C:\Users\etomas\.kre\packages\KRE-svr50-x86.1.0.0-alpha3\bin

Así si navegamos a la carpeta donde está el fichero project.json y ejecutamos “k run”:

image

En el caso de que os de un error de que no puede encontrar algún assembly lanzando un mensaje de error parecido al siguiente:

System.InvalidOperationException: Failed to resolve the following dependencies:
   Microsoft.Framework.Runtime.Common 1.0.0-alpha3

Debes ejecutar el comando “kpm restore”. Eso es necesario cuando la maquina en la que estás ejecutando no tiene alguno de los paquetes marcados como dependencias en el project.json. Si es la propia maquina en la que tienes VS14 eso no te ocurrirá (VS14 se descarga los paquetes) pero, p. ej. yo he copiado el proyecto a otra máquina donde no tenía VS14, pero sí el runtime de vNext y he necesitado ejecutar dicho comando.

Fijate en cuatro detalles:

  1. Se pasa primero por el constructor de la clase Program
  2. El framework nos inyecta automáticamente el objeto IApplicationEnvironment
  3. Luego se llama automáticamente al método Main
  4. No es necesario que el método Main sea estático.

Si te preguntas como pasar parámetros al método main, pues simplemente añadiéndolos al comando “k run”. Así, si p. ej. tecleas “k run remove /s” el método Main recibirá dos parámetros (“remove” y “/s”). Para pasar parámetros con VS debes usar las propiedades del proyecto (sección Debugging).

Podríamos implementar un comando “list” que listase todos los ficheros del proyecto:

  1. public class Program
  2. {
  3.     private readonly string _folder;
  4.     public Program(IApplicationEnvironment env)
  5.     {
  6.         _folder = env.ApplicationBasePath;
  7.     }
  8.     public void Main(string[] args)
  9.     {
  10.         if (args.Length == 1 && args[0] == "list")
  11.         {
  12.             ListFiles();
  13.         }
  14.         Console.ReadLine();
  15.     }
  16.  
  17.     private void ListFiles()
  18.     {
  19.         var files = Directory.EnumerateFiles(_folder, "*.*", SearchOption.AllDirectories);
  20.         foreach (var file in files)
  21.         {
  22.             Console.WriteLine(file);
  23.         }
  24.     }
  25. }

Para poder usar la clase Directory debes añadir una dependencia en el project.json al paquete System.IO.FileSystem (recuerda que en vNext todo el framework está fragmentado y dividido en paquetes NuGet).

Bien, ahora ya sabemos que podemos ejecutar este programa con “k run list”, pero a nosotros lo que nos interesa es tener un “comando k” adicional para usar con otro programa vNext.

Para ver como lo podemos montar vamos a agregar otro proyecto de consola de ASP.NET vNext a la solución. Yo lo he llamado DemoLauncher. No es necesario que toques nada del código.

Ahora debes agregar una dependencia desde DemoLauncher al otro proyecto (que en mi caso se llamaba TestCommands):

  1. "dependencies": {
  2.     "TestCommands": ""
  3. },

Puedes agregar esta dependencia porque, a pesar de que TestCommands no

está en NuGet está en la misma solución.

Ahora damos de alta el comando. Los comandos se dan de alta en el project.json. Así editamos el project.json de DemoLauncher para añadir una sección de commands:

  1. "commands": {
  2.     "tc" : "TestCommands"
  3. }

Con esto le indicamos que cuando se lance el comando “tc” se invoque al ensamblado “TestCommands”.

Ahora puedo ir a la carpeta donde está el project.json de DemoLauncher y si tecleo “k run” se ejecutará DemoLauncher (eso era de esperar):

image

Pero lo bueno viene ahora. Si desde esa misma carpeta tecleas “k tc list” se te listarán todos los ficheros que haya en la carpeta (y subcarpetas) de DemoLauncher:

image

Al lanzar “k tc” como en el project.json hay definido el comando “tc” se invoca al ensamblado “TestCommands” y se le pasan los parámetros que se hayan pasado después de tc. Así pues TestCommands recibe el parámetro “list” y lista todos los ficheros. Lo interesante es que TestCommands se ejecuta bajo el contexto de ejecución de DemoLauncher (la propiedad ApplicationBasePath del IApplicationEnvironment apunta al directorio donde está DemoLauncher).

En un escenario final real, tendríamos “TestCommands” publicado a NuGet, pero el resto vendría a ser lo mismo que hemos visto.

La gente de EF ya ha empezado a usar esa táctica para los comandos de Migrations (y así posibilitar el uso de Migrations en entornos no windows al no depender más de Powershell). Y personalmente creo que vamos a ver bastantes de esos futuros comandos.

Un saludo!

con 1 comment(s)
Archivado en:

En ASP.NET vNext se unifican MVC y WebApi en una nueva API llamada MVC6. Aunque MVC6 se parece a MVC5 no es compatible con ella, del mismo modo que WebApi se parece a MVC pero por debajo son muy distintas.

Ya hemos viso algunas de las novedades o cambios que trae MVC6 (temas de model binding, controladores POCO, …) y en este post vamos a explorar uno más: los ViewComponents.

Resumiendo: los ViewComponents sustituyen a las vistas parciales. Ya no existe este concepto en MVC6. De hecho, tampoco nos engañemos, desde razor la diferencia entre vistas parciales y vistas normales (a nivel del archivo .cshtml) es muy pequeña: se puede usar una vista parcial como vista normal tan solo cambiando el “return PartialView()” por un “return View()” (o viceversa). En el motor de vistas de ASPX eso no era así, ya que las vistas eran archivos .aspx y las vistas parciales eran archivos .ascx.

En Razor la única diferencia actual entre una vista parcial y una normal es que en la segunda se procesa el archivo de Layout (usualmente _Layout.cshtml) y en la primera no. Pero no es el archivo .cshtml quien determina si es vista normal o parcial. Es el ActionResult devuelto. Si devuelves un ViewResult el archivo .cshtml se procesará como vista normal. Si devuelves un PartialViewResult el archivo .cshtml se procesará como vista parcial.

El código “clásico” en MVC5 para tener una vista parcial era algo como:

  1. public ActionResult Child()
  2. {
  3.     return PartialView();
  4. }

El problema con este enfoque es que esta acción es enrutable, por lo que cualquiera puede ir a la URL que enrute esa acción (p. ej. Home/Child si suponemos HomeController) y recibirá el contenido HTML de la vista parcial. Eso, generalmente, no se desea (para algo la vista es parcial).

Para solventar esto, en MVC4 se añadió el atributo [ChildActionOnly] que evitaba que una acción se enrutase. Así si decoramos la acción con dicho atributo cuando el usuario navega a la URL que debería enrutar dicha acción recibirá un error:

image

La acción se puede invocar a través del helper Html.RenderAction:

  1. @{ Html.RenderAction("Child", "Home"); }

Nota: Se puede usar Html.Partial o Html.RenderPartial para renderizar una vista parcial directamente (sin pasar por un controlador). Eso es útil en el caso de que no haya lógica asociada a dicha vista parcial (si la hay, lo suyo es colocarla en la acción y usar Html.RenderAction).

Bueno… así tenemos las cosas hoy en día: básicamente colocamos las acciones “hijas” en un controlador (porque es donde podemos colocar lógica) pero luego las quitamos del sistema de enrutamiento (con [ChildActionOnly]) y las llamamos indicando directamente que acción y que controlador es.

Realmente ¿tiene sentido que las acciones hijas estén en un controlador? No. Porque la responsabilidad del controlador es, básicamente, responder a peticiones del navegador y eso no es una petición del navegador.

Así en MVC6 se elimina el concepto de vista parcial y el PartialViewResult, y se sustituye por el concepto de ViewComponent. Ahora lo que antes eran acciones hijas son clases propias que derivan de ViewComponent:

  1. [ViewComponent(Name = "Child")]
  2. public class ChildComponent : ViewComponent
  3. {
  4.     public async Task<IViewComponentResult> InvokeAsync()
  5.     {
  6.         return View();
  7.     }
  8. }

El atributo [ViewComponent] nos permite especificar el nombre que damos al componente. El siguiente paso es definir el método InvokeAsync que devuelve una Task<IViewComponentResult> con el resultado. La clase ViewComponent nos define el método View() que devuelve la vista asociada a dicho componente (de forma análoga al método View() de un controlador).

La ubicación por defecto de la vista asociada a un componente es /Views/Shared/Components/[NombreComponente]/Default.cshtml. Es decir en mi caso tengo el fichero Default.cshtml en /Views/Shared/Components/Child:

image

Por supuesto ahora tengo un sitio donde colocar la lógica (si la hubiera) de dicho componente: la propia clase ChildComponent.

Finalmente nos queda ver como renderizamos el componente. Ya no tenemos Html.RenderAction, si no que en su lugar usamos la propiedad Component que tienen las vistas de MVC6:

  1. @await Component.InvokeAsync("Child")

Simplemente le pasamos el nombre del componente (el mismo definido en el atributo [ViewComponent].

Y listos :)

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!

con no comments
Archivado en:

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:\Users\etomas\Desktop\curl>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!

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!

con 5 comment(s)
Archivado en: ,

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!

con no comments
Archivado en:

Otra de las cosas que cambia, radicalmente, en ASP.NET vNext es el tema de la configuración. Hasta ahora teníamos, generalmente, mezcladas en un mismo archivo (web.config) tanto la configuración propia de nuestra aplicación (app settings, connection strings y módulos de configuración propios) como la del framework (p. ej. la configuración de forms authentication o de los HttpModules).

En vNext eso cambia radicalmente. Para empezar desaparece web.config. La configuración de los distintos módulos del framework se realiza por código, en el método Configure de la clase Startup. La configuración de nuestra aplicación se mantiene en ficheros de configuración pero desaparecen las nociones de appsettings o cadenas de conexión. Ýa no hay una API más o menos “tipada” como la del ConfigurationManager (donde tenemos propiedades como ConnectionStrings o AppSettings). La configuración de vNext es básicamente un diccionario de claves, valores. Así pues los ficheros de configuración que usemos ya no tienen una estructura predefinida. Desaparece toda noción de esquema en los ficheros de configuración. De hecho se soportan varios formatos: XML, JSON y INI (si, puede ser sorprendente el soporte para INI pero debemos tener presente la aspiración “multiplataforma” de vNext y ficheros INI son muy usados en entornos Unix).

Vamos a ver un ejemplo sencillo del uso del sistema de configuración nuevo de vNext. Para ello partiremos de una aplicación vNext vacía (ASP.NET vNext Empty Web Application).

Una diferencia importante respecto a ASP.NET clásico es que la configuración ahora es un objeto. Ya no hay clases estáticas (como ConfigurationManager). Así que el primer punto es crear dicho objeto en el método Configure de la clase Startup:

  1. public void Configure(IBuilder app)
  2. {
  3.     var config = new Configuration();    
  4. }

El siguiente paso será agregar un archivo json (p. ej. data.json) con el siguiente formato:

  1. {
  2.     "environments": {
  3.         "dev": {
  4.             "background": "blue"
  5.         },
  6.           "pre":{
  7.               "background":"yellow"
  8.         },
  9.           "prod":{
  10.               "background":"red"
  11.         }
  12.     }
  13. }

El “esquema” de los datos JSON es totalmente inventado.

Bien. La clase Configuration tiene un método, llamado Add, al cual debe pasársele un IConfigurationSource. No tiene soporte para cargar distintos tipos de archivo, pero dicho soporte se obtiene a través de métodos de extensión. Uno de ellos es AddJsonConfig que está definido en el paquete Microsoft.Framework.ConfigurationModel.Json, así que debemos agregar una referencia a dicho paquete en project.json (en la sección “dependencies”):

  1. "Microsoft.Framework.ConfigurationModel.Json": "0.1-alpha-build-0233"

Ahora ya podemos usar el método “AddJsonFile”:

  1. config.AddJsonFile("data.json");

Con eso cargamos los valores de dicho fichero de configuración dentro del objeto Configuration. El objeto Configuration es un diccionario plano, pero nuestro JSON tiene profundidad (p. ej. el objeto environments tiene tres propiedades (dev, pre, prod), cada una de las cuales tiene otra propiedad llamada background. Para convertir esa estructura jerárquica a una plana, se añaden las claves usando el carácter dos puntos (:) como separador. Así, después de procesar este JSON el objeto Configuration tendrá tres claves:

  1. environments:dev:background (con el valor blue)
  2. environments:pre:background (con el valor yellow)
  3. environments:prod:background (con el valor red)

Se usa el método Get para acceder a un valor de la configuración. El método Get acepta un solo parámetro: la clave y devuelve el valor (otra cadena). Al ejecutar el siguiente código value valdría “yellow”:

  1. var value = config.Get("environments:pre:background");

Recordad: ¡no hay una sección predeterminada para guardar valores de cadenas de conexión ni appsettings! Nosotros decidimos donde guardar cada cosa que necesitemos.

Vamos a ver ahora como hacer uso de dicha configuración desde un módulo de vNext. P. ej. agreguemos ASP.NET MVC a nuestro proyecto, agregando la referencia a Microsoft.AspNet.Mvc a project.json:

  1. "Microsoft.AspNet.Mvc" :"0.1-alpha-build-1268"

Ahora añadamos el código al método Configure de la clase Startup para inicializar correctamente ASP.NET MVC. Además añadimos el objeto config dentro del sistema de inyección de dependencias de vNext:

  1. public void Configure(IBuilder app)
  2. {
  3.     var config = new Configuration();
  4.     config.AddJsonFile("data.json");
  5.     app.UseServices(
  6.         s =>
  7.         {
  8.             s.AddInstance<IConfiguration>(config);
  9.             s.AddMvc();
  10.         });
  11.     app.UseMvc();
  12. }

Ahora agrega un controlador Home y haz que tenga en su constructor un parámetro de tipo IConfiguration:

  1. public class HomeController : Controller
  2. {
  3.     private readonly string _bg;
  4.  
  5.     public HomeController(IConfiguration cfg)
  6.     {
  7.         _bg = cfg.Get("environments:dev:background");
  8.     }
  9.  
  10.     public IActionResult Index()
  11.     {
  12.         ViewBag.bg = _bg;
  13.         return View();
  14.     }
  15. }

Dado que hemos agregado la configuración dentro del mecanismo de inyección de dependencias de vNext, ahora podemos inyectar dicha configuración por el constructor. Fíjate que en el controlador no nos guardamos toda la configuración si no solo lo que necestiamos. Finalmente si te creas una vista Index.cshtml de prueba (en Views/Home):

  1. <html>
  2. <head><title>Demo Config</title></head>
  3. <body style="background: @ViewBag.bg">
  4.     <h1>Demo config</h1>
  5. </body>
  6. </html>

Ahora al ejecutar la demo deberías ver la página con el fondo azul, pues este es el valor de la entrada environments:dev:background de la configuración:

image

Y eso viene a ser todo… ¡Espero que os haya resultado interesante!

con no comments
Archivado en:

Una de las novedades que presenta ASP.NET MVC6 (integrada dentro de vNext) es la posibilidad de que los controladores ya no deban heredar de ninguna clase base.

De hecho la clase Controller en MVC clásico (MVC5 y anteriores) proporcionaba básicamente dos cosas:

  1. Un conjunto de métodos de para devolver action results (p. ej. el método View() para devolver un ViewResult o el método Json para devolver un JsonResult).
  2. Acceso a algunas propiedades para contexto (ControllerContext, ModelState y ViewBag básicamente).

Los métdos para devolver action results no son estríctamente necesarios (aunque ayudan) pero pueden encapsularse en alguna clase aparte y los objetos de contexto pueden añadirse por inyección de dependencias (ASP.NET vNext está montando desde la base usando inyección de dependencias).

Así en MVC6 podemos crear un controlador como el siguiente:

  1. public class HomeController
  2. {
  3.     // GET: /<controller>/
  4.     public IActionResult Index()
  5.     {
  6.         return null;
  7.     }
  8. }

Si (asumiendo la tabla de rutas tradicional) navegamos hacia /Home/Index veremos como se nos invoca dicho método. Por supuesto ahora hemos de ver como crear el action result necesario. P. ej. supongamos que queremos devolver la vista (lo que sería un return View() en un controlador tradicional). Vemos que el constructor del ViewResult nos pide dos parámetros:

image

Como he dicho antes ASP.NET vNext está montado basado en inyección de dependencias así que… deja que el propio framework te inyecte estos parámetros:

  1. public class HomeController
  2. {
  3.     private readonly IServiceProvider _serviceProvider;
  4.     private readonly IViewEngine _viewEngine;
  5.     public HomeController(IServiceProvider serviceProvider, IViewEngine viewEngine)
  6.     {
  7.         _serviceProvider = serviceProvider;
  8.         _viewEngine = viewEngine;
  9.     }
  10.     public IActionResult Index()
  11.     {
  12.         return new ViewResult(_serviceProvider, _viewEngine);
  13.     }
  14. }

Si ahora ejecutas y colocas un breakpoint en el constructor verás que ambos parámetros han sido inicializados por el framework de ASP.NET vNext:

image

Verás como efectivamente esto devuelve la vista Index.cshtml localizada en Views/Home (exactamente lo mismo que hace return View()).

Pasar un modelo a la vista tampoco es excesivamente complicado:

  1. public class HomeController
  2. {
  3.     private readonly IServiceProvider _serviceProvider;
  4.     private readonly IViewEngine _viewEngine;
  5.     private readonly IModelMetadataProvider _modelMetadataProvider;
  6.     public HomeController(IServiceProvider serviceProvider, IViewEngine viewEngine, IModelMetadataProvider modelMetadataProvider)
  7.     {
  8.         _serviceProvider = serviceProvider;
  9.         _viewEngine = viewEngine;
  10.         _modelMetadataProvider = modelMetadataProvider;
  11.     }
  12.     public IActionResult Index()
  13.     {
  14.         var viewdata = new ViewDataDictionary<FooModel>(_modelMetadataProvider);
  15.         viewdata.Model = new FooModel();
  16.         return new ViewResult(_serviceProvider, _viewEngine) { ViewData = viewdata };
  17.     }

Necesitamos un IModelMetadataProvider (que recibimos también por inyección de dependencias) ya que lo necesitamos para la construcción del ViewDataDictionary que pasamos a la vista.

Para evitar que nuestro controlador POCO deba tomar demasiadas depenencias en el constructor (dependencias que son requeridas básicamente para construir los action results), el equipo de ASP.NET ha creado la interfaz IActionResultHelper. Dicha interfaz contiene métodos para ayudarnos a crear más fácilmente los action results. Por supuesto, en el controlador recibimos un IActionResultHelper por inyección de dependencias. Así podemos modificar nuestro controlador para que quede de la siguiente forma:

  1. public class HomeController
  2. {
  3.     private readonly IActionResultHelper _actionHelper;
  4.     private readonly IModelMetadataProvider _modelMetadataProvider;
  5.     public HomeController(IActionResultHelper actionHelper, IModelMetadataProvider modelMetadataProvider)
  6.     {
  7.         _actionHelper = actionHelper;
  8.         _modelMetadataProvider = modelMetadataProvider;
  9.     }
  10.     public IActionResult Index()
  11.     {
  12.         var viewdata = new ViewDataDictionary<FooModel>(_modelMetadataProvider);
  13.         viewdata.Model = new FooModel();
  14.         return _actionHelper.View("Index", viewdata);
  15.     }
  16. }

Ahora el controlador solo toma una dependencia contra el IActionResultHelper y el IModelMetadataProvider. Las dependencias contra el IServiceProvider y el IViewEngine (que eran solo para crear el ViewResult) son gestionadas ahora por el IActionResultHelper.

¡Y listos! Hemos visto como podemos crear controladores POCO (que no hereden de Controller) y como a través de la inyección de dependencias ¡recibimos las dependencias necesarias de forma automática!

¡Saludos!

Lentamente se van desgranando las novedades que incorporará ASP.NET vNext. Recordad que podéis ya jugar un poco con él, descargandoos la CTP de VS14 :)

Vamos a ver en este post cuatro cosillas sobre el routing que incorpora ASP.NET vNext. Vamos a partir de una aplicación web vNext vacía y agregaremos tan solo lo justo para tener MVC y una tabla de rutas. Lo primero será editar el project.json y añadir la referencia a asp.net MVC:

  1. "dependencies": {
  2.     "Helios" : "0.1-alpha-build-0585",
  3.     "Microsoft.AspNet.Mvc": "0.1-alpha-build-1268"
  4. },

Una vez hecho esto ya podemos editar el método Configure de la clase Startup para agregar MVC y configurar una tabla de rutas:

  1.     app.UseServices(s => s.AddMvc());
  2.     app.UseMvc(r =>
  3.     {
  4.         r.MapRoute(
  5.             name: "default",
  6.             template: "{controller}/{action}/{id?}",
  7.             defaults: new { controller = "Home", action = "Index" });
  8.     });
  9. }

Con esto tenemos la “clásica” tabla de rutas por defecto. Una diferencia respecto a MVC clásico (MVC5 y anteriores) es el uso de id? para indicar un parámetro opcional (en lugar de colocar id = UrlParameter.Optional en el defaults). Esta nomenclatura es la misma que usábamos en el routing attribute de webapi 2 y es mucho más concisa.

Si añadimos un controlador HomeController para que nos devuelva una vista (Index.cshtml) veremos que todo funciona correctamente… Hasta ahí nada nuevo bajo el sol.

Pero, recordad: WebApi y MVC están ahora unificados y a nivel de routing funcionaban de forma relativamente distinta. Pero… ¿como funcionará el routing de vNext? Hagamos un experimento:

  1. public IActionResult Index()
  2.  {
  3.      return View();
  4.  }
  5.  
  6.  public IActionResult Index(int id)
  7.  {
  8.      return View();
  9.  }

En MVC clásico este código no funciona (MVC se queja de una ambigüedad) ya que dos métodos de un controlador no pueden implementar la misma acción (para el mismo verbo HTTP). Pero en WebApi esto funciona. Pues en vNext también.

Si invocamos a la acción sin pasar el parámetro id (p. ej. con / o con /Home/Index) se ejecutará el primer método. Si invocamos la acción pasando el parámetro id (p. ej. con /Home/Index/2) se ejecutará el segundo método.

Prosigamos nuestro experimento… Modifiquemos el método Startup.Configure para tener la siguiente tabla de rutas:

  1. app.UseMvc(r =>
  2. {
  3.     r.MapRoute(
  4.         name: "default",
  5.         template: "{controller}/{action}/{id?}",
  6.         defaults: new { controller = "Home", action = "Index" });
  7.  
  8.     r.MapRoute(
  9.         name: "second",
  10.         template: "Details/{id?}",
  11.         defaults: new { controller = "Product", action="Display"});
  12. });

Creamos el controlador ProductController con una acción Dispay:

  1. public IActionResult Display(int? id)
  2. {
  3.     if (id == null) ViewBag.Pid = "None";
  4.     else ViewBag.Pid = id;
  5.     return View();
  6. }

Si ahora vamos a /Details/100.. ¿qué es lo que obtenemos? En ASP.NET MVC clásico obtendremos un 404 porque las rutas se evalúan en orden y la primera que es posible aplicar se aplica. Así la url /Details/100 encaja dentro de la primera ruta con:

  • controller = Details
  • action = 100
  • id = sin valor

Y como no existe el DetailsController de ahí el 404 que obtendríamos con MVC clásico. De ahí que la tabla de rutas tenga que configurarse con las rutas en orden desde la más específica a la más general.

Pero en vNext eso ya no es así. Las rutas siguen evaluándose en orden pero si una ruta devuelve un 404 el control pasa a la siguiente ruta. Así, en este caso se intenta evaluar la primera ruta con los parámetros indicados anteriormente (al igual que en MVC clásico). Dicha ruta devuelve un 404 así que el control pasa a la siguiente ruta. La URL /Details/10 encaja con el patrón de la siguiente ruta así que se intenta aplicar dicha ruta con los parámetros:

  • controller = Product (sacado de defaults)
  • action = Display (scado de defaults)
  • id = 100

Ahora como el controlador y la acción existen se invocan y vemos la página de detalles del producto 100.

Hagamos una tercera modificación a la tabla de rutas para que quede así:

  1. app.UseMvc(r =>
  2. {
  3.     r.MapRoute(
  4.         name: "default",
  5.         template: "{controller}/{action}/{id?}",
  6.         defaults: new { controller = "Home", action = "Index" });
  7.  
  8.     r.MapRoute(
  9.         name: "second",
  10.         template: "Details/{id?}",
  11.         defaults: new { controller = "Product"});
  12. });

Es la misma tabla de rutas que teníamos con una excepción importante: la ruta “second” no define acción.

Si ahora navegamos de nuevo a /Details/100 obtenemos un 404. Era de esperar, puesto que no hay acción alguna… Pero el routing de vNext nos tiene reservada una última sorpresa: si no hay acción definida se enrutará por el verbo HTTP.

Para hacer la prueba modifica el controlador Product:

  1. public IActionResult Get(int? id)
  2. {
  3.     if (id == null) ViewBag.Pid = "None";
  4.     else ViewBag.Pid = id;
  5.     return View("Display");
  6. }

Si ahora navegas a /Details/100 deberías ver, de nuevo, los detalles del producto con id 100. Al no haber acción vNext ha enrutado por el verbo HTTP y nuestro controlador se ha enrutado como si fuese un controlador de webapi.

Bueno… hemos visto brevemente algunas de las diferencias entre el routing de vNext y el de MVC tradicional. Para más detalles os recomiendo este post del equipo de ASP.NET.

Saludos!

Muy buenas! Acabo de leer el post de Luis Ruiz Pavon sobre sobre el sobreescribir .ToString() para mejorar la información del modo de depuración de VS.

Y ya puestos, para complementar su post, quería comentar un truco que no se si conoce mucha gente y que permite algo parecido sin necesidad de sobreescribir ToString (que se usa para otras cosas además de para mostrar la información en la ventana de depuración) y que es el uso del atributo DebuggerDisplay.

En este caso un ejemplo vale más que mil palabras:

  1.    [DebuggerDisplay ("Product {Name} ({Code}): {Description}")]
  2.     public class Product
  3.     {
  4.         public string Code { get; set; }
  5.         public string Name { get; set; }
  6.         public string Description { get; set; }
  7.         public Product(string code, string name, string description)
  8.         {
  9.             Code = code;
  10.             Name = name;
  11.             Description = description;
  12.         }
  13.         public override string ToString()
  14.         {
  15.             return String.Format("Code - {0}", Code);
  16.         }
  17.     }

Y el resultado en la ventana de watches es:

image

Podemos conseguir cosas más interesantes utilizando las propiedades Name y TargetTypeName que modifican los valores de las columnas “Name” y “Type”. Así si uso el siguiente [DebuggerDisplay]:

  1. [DebuggerDisplay ("({Code}): {Description}", Name = "Product {Name}", Type ="Producto")]

Veo lo siguiente en la ventana de watches:

image

Y en la ventana de Locals veo, ahora, lo siguiente:

image

El uso de DebuggerDisplay tiene sus limitaciones si se compara con redefinir ToString() (este último es un método y puede contener código como el que mostraba Luis en su post para convertir a JSON), mientras que DebuggerDisplay tan solo puede generar una pequeña cadena a partir de las propiedades.

Si una clase tiene tanto ToString() redefinido como DebuggerDispay este último tiene precedencia para el depurador.

¡Y… eso es todo! :)

Saludos!

Nota: Este post está realizado sobre la CTP de VS2014 y la alfa de ASP.NET vNext. Todo lo dicho puede cambiar cuando salgan las versiones RTM…

¿Conoces Nowin? Es un servidor web OWIN, es decir si construimos nuestra aplicación web basándonos en middleware OWIN (p. ej. WebApi 2 o NancyFx) podemos usar este servidor web para servirla a los clientes. Ya he hablado de OWIN varias veces antes, ya que OWIN ha sido la penúltima revolución en desarrollo de aplicaciones web en tecnología Microsot (la última es vNext, claro). Microsoft hizo un esfuerzo implementando middleware OWIN y permitiendo su integración con ASP.NET (el proyecto Katana), pero ahora sale ASP.NET vNext y es lógico preguntarse… ¿en qué queda todo?

Básicamente la pregunta es si ASP.NET vNext es compatible con OWIN y si lo es, en que terminos: ¿podemos añadir componentes OWIN al pipeline de vNext y podemos usar componentes vNext como si fuesen componentes OWIN?

La respuesta rápida a las tres preguntas es sí. ASP.NET vNext es compatible con OWIN y podemos tanto añadir componentes OWIN a nuestro pipeline de ASP.NET vNext como usar un componente vNext como si fuese middleware OWIN. De hecho cualquier otra respuesta sería totalmente incomprensible.

La idea de modularización de la aplicación en componentes que tiene ASP.NET vNext está sacada directamente de OWIN (en Katana se usa IAppBuilder lo que generó un interesantísimo debate en la comunidad).

Bueno, vayamos al tajo… intentemos ver como podemos servir una aplicación ASP.NET vNext (MVC6) pero usando un servidor OWIN como Nowin para ello. Lo primero es crear una “ASP.NET vNext Console Application” usando VS2014 CTP… Eso nos permitirá ver como hospedar ASP.NET vNext en una aplicación de consola ;)

El siguiente paso será instalar MVC6 en nuestra aplicacion. No usaremos NuGet para ello, en su lugar editaremos el fichero project.json que trae VS2104 y añadiremos las siguientes líneas en la sección dependencies:

  1. "Microsoft.AspNet.Mvc": "0.1-alpha-build-*"

Por suerte VS2014 nos ofrece intellisense al editar project.json (aunque parecen no salir todas las referencias de paquetes NuGet):

image

Ahora añade un controlador de MVC a tu aplicación (HomeController) con una acción Index que simplemente retorne la vista asociada:

  1. using Microsoft.AspNet.Mvc;
  2.  
  3. namespace NowinDemo.Controllers
  4. {
  5.     public class HomeController : Controller
  6.     {
  7.         public IActionResult Index()
  8.         {
  9.             return View();
  10.         }
  11.     }
  12. }

Una vez hecho esto añade (en la carpeta /Views/Home) la vista Index.chstml:

  1. <html>
  2. <head><title>Nowin Test</title></head>
  3. <body>
  4.     <p>Running on Nowin... @DateTime.Now.ToShortDateString()</p>
  5. </body>
  6. </html>

Ya tenemos el controlador y la vista de MVC. Tan solo nos falta “configurar” nuestra aplicación para que use ASP.NET MVC. Para ello nos creamos una clase llamada Startup:

  1. public class Startup
  2. {
  3.     public void Configure(IBuilder app)
  4.     {
  5.  
  6.         app.UseServices(services =>
  7.         {
  8.             services.AddMvc();
  9.         });
  10.  
  11.         app.UseMvc();
  12.  
  13.     }
  14. }

El siguiente paso es añadir las referencias necesarias para hospedar nuestra aplicación web y a Nowin. Para ello editamos el archivo project.json para añadir en la sección “dependencies”:

  1. "Nowin" :  "0.11.0",
  2. "Microsoft.AspNet.Owin": "0.1-alpha-build-*",
  3. "Microsoft.AspNet.Hosting": "0.1-alpha-build-*"

(Fíjate que al guardar el fichero project.json y al compilar te saldrán ya las referencias en VS2014)

Ya tenemos Nowin agregado, ahora toca configurarlo todo. Lo primero es cargar nuestra clase Startup. Eso es necesario porque es una aplicación de consola. En este caso debemos añadir el siguiente código a Program.cs:

  1. public class Program
  2. {
  3.     private readonly IServiceProvider _hostServiceProvider;
  4.  
  5.     public Program(IServiceProvider hostServiceProvider)
  6.     {
  7.         _hostServiceProvider = hostServiceProvider;
  8.     }
  9.  
  10.     public Task<int> Main(string[] args)
  11.     {
  12.         return Task.FromResult(0);
  13.     }
  14. }

Sí: es un código un poco raro para ser un programa de consola, verdad? Por un lado Program tiene un constructor que recibe un IServiceProvider y por otro el método Main no es estático si no que devuelve una Task<int>. Y esto? Esto es porque la aplicación de consola se pone en marcha a través de KRE (el runtime de ASP.NET vNext).

Bien, ahora vamos a colocar código en Main para inicializar nuestr aplicación ASP.NET vNext:

  1. var serviceCollection = new ServiceCollection();
  2. serviceCollection.Add(HostingServices.GetDefaultServices());
  3. var services = serviceCollection.BuildServiceProvider(_hostServiceProvider);

Bien, ahora que ya lo tenemos todo configurado, nos queda poner en marcha el host. Antes que nada, un pequeño recordatorio, sobre como establecíamos el servidor web usando OWIN y Katana en una aplicación de consola. En Katana usábamos el paquete Microsoft.Owin.Hosting, que define una clase llamada WebApp<T> y usábamos un código parecido a:

var options = new StartOptions
{
    ServerFactory = "Nowin",
    Port = 8080
};
using (WebApp.Start<Startup>(options))
{
    Console.WriteLine("Running a http server on port 8080");
    Console.ReadKey();
}

En este contexto la clase Startup (parámetro genérico de WebApp) era la clase de configuración de la aplicación (lo equivalente a nuestra clase Startup). Lo interesante es la la StartOptions que permitía establecer el ensamblado que contiene el servidor web (en este caso Nowin).

En vNext la idea es muy similar (cambian clases y propiedades) pero es la misma idea:

  1. var context = new HostingContext()
  2. {
  3.     Services = services,
  4.     ServerName= "Nowin",
  5.     ApplicationName = "NowinDemo",
  6. };
  7. var engine = services.GetService<IHostingEngine>();
  8. using (engine.Start(context))
  9. {
  10.     Console.WriteLine("Server waiting... ");
  11.     Console.ReadLine();
  12. }

Nota: NowinDemo es el nombre de mi ensamblado (el que contiene la clase Startup) y Nowin es obviamente el nombre del ensamblado de Nowin.

Directamente establezco que el servidor está en el ensamblado Nowin. No obstante esto no funciona:

image

La razón es que, efectivamente ASP.NET vNext intenta ir a este ensamblado para obtener un servidor, pero ahora espera un servidor vNext y no uno OWIN como Nowin. En concreto espera que haya una clase que implemente IServerFactory (interfaz de vNext).

Bueno, por suerte, no es excesivamente complejo crear dicha clase. Así pues añadimos una clase que implemente IServerFactory:

  1.     public class NowinServerFactory : IServerFactory
  2.     {
  3.         public IServerInformation Initialize(IConfiguration configuration)
  4.         {
  5.         }       
  6.         public IDisposable Start(IServerInformation serverInformation, Func<object, Task> application)
  7.         {
  8.   
  9.         }
  10.     }

El método Initialize se llama una vez al principio de la aplicación y debemos devolver una clase que implemente IServerBuilder. Dicha interfaz tan solo define una propiedad llamada Name. Vamos a crearnos una clase que nos implemente dicha interfaz:

  1. class NowinServerInformation : IServerInformation
  2. {
  3.     public string Name { get { return "Nowin"; } }
  4. }

En el método Initialize debemos instanciar el servidor Nowin y devolver un objeto IServerInformation:

  1. public IServerInformation Initialize(IConfiguration configuration)
  2. {
  3.     var builder = ServerBuilder.New()
  4.         .SetAddress(IPAddress.Any)
  5.         .SetPort(8080)
  6.         .SetOwinApp(HandleRequest);
  7.     return new NowinServerInformation();
  8. }

Nos falta el método HandleRequest (un Func<IDictionary<string, object>, Task> que espera SetOwinApp:

  1. private Task HandleRequest(IDictionary<string, object> env)
  2. {
  3.     return _callback(new OwinFeatureCollection(env));
  4. }

La variable _callback es un Func<object, Task>. Lo interesante es el uso de la clase OwinFeatureCollection (definida en Microsoft.AspNet.Owin) que ofrece un “envoltorio” tipado al diccionario de entorno de Owin.

Ahora en el método Start del objeto IServerFactory debemos devolver el componente que es el servidor. Este método recibe el IServerInformation y la definición de aplicación (es decir el conjunto de componentes del pipeline vNext) como una Func<object, Task>.  El problema ahora lo tenemos en el hecho de que debemos obtener el servidor Nowin (INowinServer) a partir del objeto IServerInformation… pero allí no lo hemos guardado. Así pues debemos redefinir dicha clase, para que nos guarde la información necesaria para ello. La idea es que la clase que implementa IServerFactory debe ser, como su nombre indica, la factoría para permitir crear el servidor a cada petición. Así pues el código final queda como sigue:

  1. public class NowinServerFactory : IServerFactory
  2. {
  3.     private Func<object, Task> _callback;
  4.  
  5.     private Task HandleRequest(IDictionary<string, object> env)
  6.     {
  7.         return _callback(new OwinFeatureCollection(env));
  8.     }
  9.  
  10.     public IServerInformation Initialize(IConfiguration configuration)
  11.     {
  12.         var builder = ServerBuilder.New()
  13.             .SetAddress(IPAddress.Any)
  14.             .SetPort(8080)
  15.             .SetOwinApp(HandleRequest);
  16.         return new NowinServerInformation(builder);
  17.     }
  18.             
  19.     public IDisposable Start(IServerInformation serverInformation, Func<object, Task> application)
  20.     {
  21.         var information = (NowinServerInformation)serverInformation;
  22.         _callback = application;
  23.         INowinServer server = information.Builder.Build();
  24.         server.Start();
  25.         return server;
  26.     }
  27.  
  28.  
  29.     private class NowinServerInformation : IServerInformation
  30.     {
  31.         public NowinServerInformation(ServerBuilder builder)
  32.         {
  33.             Builder = builder;
  34.         }
  35.  
  36.         public ServerBuilder Builder { get; private set; }
  37.  
  38.         public string Name
  39.         {
  40.             get
  41.             {
  42.                 return "Nowin";
  43.             }
  44.         }
  45.     }
  46. }

Nota: El código de esta clase es mérito de David Fowler y lo tenéis en este Gist.

Ahora ya tan solo nos queda modificar el HostContext puesto que el ensamblado que contiene el IServerFactory ya no es Nowin sino nuestra propia aplicación (NowinDemo en mi caso):

  1. var context = new HostingContext()
  2. {
  3.     ServerName= "NowinDemo",
  4.     ApplicationName = "NowinDemo",
  5.     Services = services,
  6. };

¡Y con esto ya hemos terminado! Tenemos una aplicación ASP.NET vNext MVC6 servida desde línea de comandos y usando un servidor web OWIN como Nowin para servirla (en lugar del clásico Microsoft.AspNet.Server.WebListener de ASP.NET vNext).

¿Donde está el código compilado?

Si compilas el proyecto en VS2014 CTP verás que en la carpeta Bin/Debug… no hay nada! No hay un NowinDemo.exe ni nada parecido. Las aplicaciones ASP.NET vNext, incluso si son de consola, se ponen en marcha a través del runtime de ASP.NET. Para ello se usa el comando K run “path_de_la_aplicación”.

El fichero K.cmd está donde tengas instalado el KRE. Por defecto está en %HOME%\.kre\packages\KRE-svrc50-x86.0.1-alpha-build-0446\bin

Si no lo tienes debes añadir al path este directorio, irte donde tienes el proyecto de VS2014 (en el path raíz del proyecto donde tienes el project.json) y teclear:

K run

¡Y listos! Tu aplicación de consola ASP.NET vNext ya está en marcha!

con 3 comment(s)
Archivado en:

Muy buenas! Esos días he estado jugando con VS2014 CTP. Esta versión no puede instalarse side by side con cualquier versión anterior de VS, así que la he instalado en una máquina virtual en Azure... La verdad es que es el mecanismo más rápido para probarlo, puesto que ha hay una plantilla de MV en Azure que contien VS2014 CTP. Vamos, que en cinco minutos pasas de no tener nada a estar ya trasteando con el VS2014. ¡Genial!

Una de las grandes novedades de este VS2014 es el tooling para ASP.NET vNext. Si bien ya hace algunos días que se podía descargar ASP.NET vNext y juguetear con ella, no era posible usar VS para ello, teniendo que recurrir a las herramientas de líneas de comandos.

Para ver de que se compone una aplicación ASP.NET vNext he abierto el VS2014 y he creado una nueva ASP.NET vNext web Application.

La diferencia más importante respecto a una web clásica es que ya no existen ni global.asax ni web.config. Ahora tanto los elementos de configuración que se incluían dentro de web.config como la inicialización de la aplicación que se realizaba en global.asax se unifican en un mismo archivo (que por defecto se llama Startup.cs).

Dicho archivo contiene una clase (llamada Startup) que es invocada automáticamente por el runtime de ASP.NET vNext para realizar la inicialización de la aplicación. Nos encontramos ante un nuevo ejemplo de convention over configuration. No es necesario configurar nada: crea una clase llamada Startup y esa pasará a ser la clase que inicialice toda tu aplicación. Eso no es nuevo de vNext, ya en Katana ocurría algo muy similar.

Dicha clase Startup debe contener un método llamado Configure que espera un parámetro de tipo IBuilder. Dicha interfaz está definida de la siguiente manera:

    public interface IBuilder
    {
        IServiceProvider ApplicationServices { get; set; }
        IServerInformation Server { get; set; }

        RequestDelegate Build();
        IBuilder New();
        IBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
    }

El método importante es el método Use que permite "enchufar" un componente al pipeline de tu aplicación. Ten presente que ASP.NET vNext es totalmente modular de forma que el primer paso al configurar una aplicación es enchufar todos los componentes. Así ahora, ASP.NET MVC6 es un componente, al igual que lo es WebApi 3 y al igual que lo es un sistema para autenticarnos con cookies. Si conoces Katana es exactamente la misma idea.

El método Use es muy genérico pero raras veces lo usarás directamente: lo normal es quc cada componente proporcione un método de extensión sobre IBuilder para enchufar dicho componente. Así para enchufar ASP.NET MVC usarás el método UseMvc. Pero al final todos esos métodos de extensión terminan llamando al genérico Use.

Creación de un componente (middleware) ASP.NET vNext

Vamos a ver como crear un componente (también se les llama middleware) ASP.NET vNext. Para ello agrega una clase cualquiera a tu proyecto y añádele el siguiente código:

public class UfoMiddleware
{
    private readonly RequestDelegate _next;

    public UfoMiddleware(RequestDelegate next)
    {
        this._next = next;
    }

    public async Task Invoke(HttpContext ctx)
    {
        Debug.WriteLine("UfoMiddleware::Entrada");
        await _next(ctx);
        Debug.WriteLine("UfoMiddleware::Salida");
    }
}

Eso es todo lo que necesitas para crear un middleware (componente) de ASP.NET vNext. El parámetro next del constructor te lo mandará ASP.NET vNext (y es el componente siguiente a invocar). En el método Invoke realizas las acciones que quieras y luego llamas de forma asíncrona al middleware siguiente. Por supuesto puedes colocar código cuando el middleware siguiente ha terminado su ejecución (pero ten presente que el middleware siguiente llamará al siguiente y así sucesivamente). Así, si tienes 3 componentes (A, B y C) encadenados en este orden se ejecutará:

  1. El código de A de antes del await
  2. El código de B de antes del await
  3. El código de C de antes del await
  4. El código de C de después del await
  5. El código de B de después del await
  6. El código de A de después del await

Si te preguntas en qué orden se ejecutan los componentes, pues es en el orden el que estén registrados en IBuilder. Es decir el orden en que se llamen los métodos Use.

Para añadir nuestro componente en el pipeline de ASP.NET vNext podemos llamar al método Use de IBuilder:

 app.Use(next => new UfoMiddleware(next).Invoke);

Aunque lo más habitual sería que UfoMiddleware proporcionase un método de extensión sobre IBuilder:

public static IBuilder UseUfo(this IBuilder builder)
{
    return builder.Use(n => new UfoMiddleware(n).Invoke);
}

Y así podríamos llamar a app.UseUfo() en el Startup. La otra gran ventaja de los métodos de extensión es que permiten pasar parámetros de configuración a nuestro middleware. Así que bueno, lo normal es que cada componente venga con su método de extensión.

Ahora podemos modificar el método Invoke de nuestro UfoMiddleware para que haga "visible":

public async Task Invoke(HttpContext ctx)
{
    var path = ctx.Request.Path;
    if (ctx.Request.Path.Value == "/Help.ufo")
    {
        using (StreamWriter outfile = new StreamWriter(ctx.Response.Body))
        {
            outfile.Write("Generated by Ufo Middleware");
        }
    }
    await _next(ctx);
}

Si ahora ejecutamos de nuevo nuestra aplicación y vamos a la URL /Help.ufo (ojo, que esa url es case-sensitive) veremos que por el navegador nos sale "Generated by Ufo Middleware". Perfecto! Ya hemos hecho un middleware de ASP.NET vNext funcional (vale, no es muy útil, pero funcional es).

En siguientes posts iré desgranando distintos aspectos de ASP.NET vNext porque la verdad... ¡es apasionante!

con no comments
Archivado en: ,

Buenas! Hace poco Pedro Hurtado ha escrito un post titulado “Una evidencia, una frikada y un despropósito”. En él habla de varias cosas, relacionadas con la seguridad de aplicaciones web, pero quiero centrarme en un solo punto (el despropósito). Básicamente lo que dice Pedro es que si usas autenticación por cookies en WebApi eres vulnerable a ataques CSRF.

Vayamos por partes…

Antes que nada debemos aclarar que es un ataque CSRF. No es sencillo de definir, pero básicamente se trata de una vulnerabilidad que explota la confianza que un sitio web tiene en un usuario (previamente autenticado en dicho sitio web). La explicación de la wikipedia no está nada mal (aunque mejor la entrada inglesa).

El ejemplo que pone Pedro es el siguiente, suponiendo que tenemos habilitada la autenticación por cookies y que el usuario se haya logado en la aplicación web (es decir, por lo tanto que la cookie esté emitida), una página web maliciosa puede hacer un ataque CSRF con algo tan simple como:

  1. <p>[FROM ATTACKER] You have been hacked??? Who knows...</p>
  2.  
  3. <div style="display:none">
  4.     <form id="attacker" method="post" action="http://localhost:35815/api/prueba">
  5.         <input type="hidden" name="valor" value="hacked" />
  6.         <input type="hidden" name="valor2" value="csrf" />
  7.     </form>
  8. </div>
  9.  
  10. @section scripts {
  11.     <script>
  12.         $("#attacker").submit();
  13.     </script>
  14. }

Este código estará en cualquier vista de otra aplicación web distinta a la que define el controlador web api. En mi caso en mi proyecto tengo:

image

El proyecto web CSRFVictim define el controlador WebApi llamado Prueba con el método Post. Tengo pues las dos aplicaciones ejecutándose en IISExpress:

image

La aplicación CSRFAttacker es la que tiene la vista anterior. Cuando navego a http://localhost:35594/Home/Attack (una URL de CSRFAttacker) se termina ejecutando el método Post del controlador Prueba de CSRFVictim. ¡Ojo! Con la condición de que ANTES el usuario se haya autenticado en la aplicación CSRFVictim.

Esto tiene toda la lógica del mundo: al autenticarse en CSRFVictim se genera una cookie de autenticación. Cuando luego se navega a CSRFAttacker desde el mismo navegador se hace el POST hacia CSRFVictim, el navegador envía la cookie de autenticación… Para el controlador WebApi es, a todos los efectos, como si la petición viniese de CSRFVictim. Es una petición válida: tiene la cookie de autenticación. Porque el navegador la envía. Insisto: es necesario que el usuario esté autenticado antes en CSRFVictim.

De esto podemos desprender un par de cosillas:

  1. Intenta estar logado en cualquier sitio web el mínimo tiempo posible. Y mientras estés logado en un sitio evita visitar otras URLs (especialmente las no confiables). Evita siempre el uso de opciones tipo “remember me”. Son cookies que carga el demonio.
  2. Vigila con las llamadas POST a tu aplicación. Si te autenticas por cookies eres vulnerable a CSRF. Con independencia de si usas WebApi, un controlador MVC o cualquier otra cosa. Si no haces nada más, eres por defecto vulnerable.

Si en lugar de un controlador WebApi usas un controlador MVC que responde a un POST, que se origina en un formulario HTML (p. ej. un formulario de login) puedes protegerte utilizando HtmlHelper.AntiForgeryToken(). Mira este post para los detalles: http://blog.stevensanderson.com/2008/09/01/prevent-cross-site-request-forgery-csrf-using-aspnet-mvcs-antiforgerytoken-helper/

Pero si usas WebApi no puedes usar la técnica de AntiForgeryToken, porque esa se basa en generar un input hidden… eso funciona para vistas HTML pero no para controladores que están pensados para ser llamados programáticamente. Esto nos lleva a otro consejo:

  • Evita, siempre que puedas, la autenticación por cookies en tus servicios web api.

La autenticación basada en cookies es la causa de la vulnerabilidad. Pero muchas veces se generan controladores web api para ser usados desde dentro de una aplicación web (p. ej. para soporte de llamadas Ajax). En este caso es muy útil que la misma cookie que autentique el usuario en la aplicación web, nos sirva para los servicios web api, ya que estos están pensados para ser llamados (via javascript) desde la propia aplicación web. En este caso, deberías añadir al menos algún mecanismo de seguridad adicional.

P. ej. podrías usar algo parecido a lo siguiente:

  1.   public class AntiCSRFHandler : DelegatingHandler
  2.   {
  3.       protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  4.       {
  5.  
  6.           var referrer = request.Headers.Referrer;
  7.           var uri = request.RequestUri;
  8.  
  9.             if (referrer != null && uri != null &&referrer.Port == uri.Port && referrer.Host == uri.Host)
  10.           {
  11.  
  12.               return base.SendAsync(request, cancellationToken);
  13.           }
  14.           var response = request.CreateResponse(HttpStatusCode.InternalServerError);
  15.           response.ReasonPhrase = "Invalid referrer";
  16.           return Task.FromResult<HttpResponseMessage>(response);
  17.  
  18.       }
  19.   }

Esto crea un message handler que analiza todas las peticiones web a los servicios web api y si el referrer no es el mismo (port y host) que la url del servicio web, devuelve un HTTP 500.

Por supuesto tenemos que añadir el message handler a la configuración de Web Api:

  1. GlobalConfiguration.Configuration.MessageHandlers.Add(new AntiCSRFHandler());

Ahora si la página atacante intenta realizar el POST recibirá un HTTP 500:

image

Eso es porque el referrer contiene la URL que genera la petición (la url de la página Home/Attack de CSRFAttacker (en mi caso es http://localhost:35594/Home/Attack que es de distinto host y/o port que la URL del servicio web (http://localhost:35815/api/prueba).

Con eso tan simple ya estamos protegidos de una página atacante. Por supuesto, esa protección no es infalible, pero es relativamente segura: Mediante código HTML no se puede establecer el referrer de una página y mediante JavaScript (usando XMLHttpRequest) en principio tampoco. E incluso si se pudiese mediante XMLHttpRequest no sería un problema muy grave ya que entonces la llamada fallaría por CORS (a menos que habilites CORS en WebApi lo que no debes hacer nunca en el caso de un servicio web api que tan solo se utiliza desde la propia aplicación web).

Así pues recuerda: solo debes permitir autenticación por cookies en tus servicios web api si y solo si, son servicios web api pensados para ser usados tan solo desde la propia aplicación web. Y si lo haces asegúrate de establecer algún mecanismo adicional de seguridad para evitar CSRF.

Como siempre digo, debemos ser responsables de lo que hacemos y debemos entender lo que sucede. Es nuestro código y por lo tanto nuestra responsabilidad.

Un saludo!

con 3 comment(s)
Archivado en: ,

Imagina que estás desarrollando un proyecto, con ASP.NET MVC y cuando llevas digamos unas, no sé, cincuenta pantallas, todas llenas con sus formularios, algunos que hacen peticiones AJAX, otros que no… bueno, imagina que cuando llevas ya bastantes vistas hechas, aparece un nuevo requisito, de aquellos que están agazapados, esperando el momento propicio para saltate a la yugular: Todos los datos que entre el usuario, deben ser guardados y mostrados en mayúsculas.

Hay varias técnicas que puedes empezar a usar: Podríamos usar text-transform de CSS para transformar lo que ve el usuario en mayúsculas. Pero eso no sirve porque los datos que el servidor recibiría serían tal y como los ha entrado el usuario (text-transform cambia solo la representación pero no el propio texto).

Otra opción, claro está, es modificar todos los viewmodels porque al enlazar las propiedades las conviertan en mayúsculas. Así, si el usuario entra “edu” en la propiedad Name de un viewmodel, el viewmodel lo convierte a EDU y listos. Pero claro… si tienes muchos viewmodels ya hechos, eso puede ser muy tedioso. Y si no los tienes, pero vas a tenerlos, pues también…

En este dilema estaba un compañero de curro, cuando me planteó exactamente dicho problema. La cuestión era si había una manera que no implicase tener que tocar todos los viewmodels, para asegurar que recibíamos los datos siempre en mayúsculas. Y por suerte, la hay.

Si conoces un poco como funciona ASP.NET MVC internamente, sabrás que hay dos grupos de objetos que cooperan entre ellos para traducir los datos de la petición HTTP a los parámetros de la acción del controlador (que suele ser un viewmodel en el caso de una acción invocada via POST). Esos dos grupos de objetos son los value providers y los model binders.

La idea es muy sencilla: Los value providers recogen los datos de la petición HTTP y los dejan en un “saco común”. Los model binders recogen los datos de ese “saco común” y con esos datos recrean los valores de los parámetros de los controladores. De esa manera los value providers tan solo deben entender de la petición HTTP y los model binders solo deben entender como recrear los parámetros del controlador. Separación de responsabilidades.

Hay varios value providers porque cada uno de ellos se encarga de una parte de la petición HTTP. Así uno se encarga de los datos en la query string, otro de los form values (datos en el body usando application/x-www-form-urlencoded), otro de los datos en json… Y hay varios model binders, porque clases específicas pueden tener necesidades específicas de conversión. Aunque el DefaultModelBinder que viene de serie puede con casi todo, a veces es necesario crearse un model binder propio para suportar algunos escenarios.

En este caso la solución pasa por usar un value provider nuevo. Un value provider que recogerá los datos de la petición HTTP y los convertirá a maýsuculas antes de enviarlos a los model binders. Con eso los model binders recibirán los datos ya en mayúsculas, como si siempre hubiesen estado así. Solucionado el problema.

Veamos el código brevemente.

Lo primero es crear la factoría que cree el nuevo value provider. Los value providers se crean y se eliminan a cada petición, y el responsable de hacerlo es una factoría, que es el único objeto que existe durante todo el ciclo de vida de la aplicación:

  1. public class ToUpperValueProviderFactory : ValueProviderFactory
  2. {
  3.     private readonly ValueProviderFactory _originalFactory;
  4.     public ToUpperValueProviderFactory(ValueProviderFactory originalFactory)
  5.     {
  6.         _originalFactory = originalFactory;
  7.     }
  8.     public override IValueProvider GetValueProvider(ControllerContext controllerContext)
  9.     {
  10.         var provider = _originalFactory.GetValueProvider(controllerContext);
  11.         return provider != null ? new ToUpperProvider(provider) : null;
  12.     }
  13. }

La idea es muy sencilla: dicha factoría delega en la factoría original para obtener el value provider. Y luego devuelve un ToUpperProvider que va a ser nuestro value provider propio:

  1. public class ToUpperProvider : IValueProvider
  2. {
  3.     private readonly IValueProvider _realProvider;
  4.     public ToUpperProvider(IValueProvider realProvider)
  5.     {
  6.         _realProvider = realProvider;
  7.     }
  8.     public bool ContainsPrefix(string prefix)
  9.     {
  10.         return _realProvider.ContainsPrefix(prefix);
  11.     }
  12.     public ValueProviderResult GetValue(string key)
  13.     {
  14.         var result = _realProvider.GetValue(key);
  15.         if (result == null)
  16.         {
  17.             return null;
  18.         }
  19.         var rawString = result.RawValue as string;
  20.         if (rawString != null)
  21.         {
  22.             return new ValueProviderResult(rawString.ToUpperInvariant(),
  23.                 result.AttemptedValue.ToUpperInvariant(),
  24.                 result.Culture);
  25.         }
  26.         var rawStrings = result.RawValue as string[];
  27.         if (rawStrings != null)
  28.         {
  29.             return new ValueProviderResult(
  30.                 rawStrings.Select(s => s != null ? s.ToUpperInvariant() : null).ToArray(),
  31.                 result.AttemptedValue.ToUpperInvariant(),
  32.                 result.Culture);
  33.         }
  34.         return result;
  35.     }
  36. }

La clase ToUpperProvider implementa la interfaz IValueProvider, pero delega en el value provider original.

Lo único que hace es, en el método GetValue, una vez que tiene el valor original obtenido por el value provider original convertirlo a mayúsculas. Tratamos dos casuísticas: que el valor sea una cadena o un array de cadenas.

¡Y listos!

El último paso es configurar ASP.NET MVC. P. ej. para hacer que todos los datos enviados via POST usando form data (es decir, un submit de form estándard) se reciban en mayúsculas basta con hacer (en el Application_Start de Global.asax.cs):

  1. var old = ValueProviderFactories.Factories.OfType<FormValueProviderFactory>().FirstOrDefault();
  2. if (old != null)
  3. {
  4.     ValueProviderFactories.Factories.Remove(old);
  5.     ValueProviderFactories.Factories.Add(new ToUpperValueProviderFactory(old));
  6. }

Con eso sustituimos la factoría original que devuelve el value provider que se encarga de los datos en el form data por nuestra propia factoría.

Para probarlo basta con crear una vista con un formulario y hacer un post normal y corriente… y verás que todos los datos que se entren se pasarán a mayúsculas automáticamente :)

Saludos!

con 1 comment(s)
Archivado en:

En el post anterior vimos todo la parte teórica de HTTP que nos permite realizar descargas de ficheros, pausarlas y continuarlas. Pero ahora viene lo bueno… Vamos a implementar el soporte en el servidor para soportar dichas descargas.

Dado que nuestro amado FileStreamResult no soporta la cabecera Range, nos va a tocar a nostros hacer todo el trabajo. Pero, la verdad… no hay para tanto.

Nota importante: Todo, absolutamente todo el código que pongo en este blog está para que hagas con él lo que quieras. Pero del mismo modo, el código que hay en este blog NO ES CÓDIGO DE PRODUCCIÓN. En muchos casos no está suficientemente probado y en otros, para simplificar, no se tienen en cuenta todas las casuísticas o no hay apenas control de errores. Si quieres hacer copy/paste eres totalmente libre de hacerlo, pero revisa luego el código. Entiéndelo y hazlo tuyo.

Bien, el primer paso va a ser crearnos un ActionResult nuevo, en este caso una clase llamada RangeFileActionResult y que herederá pues de ActionResult. En dicho ActionResult vamos a obtener el valor del campo Range y a parsearlo. Empecemos por el constructor:

  1. public RangeFileActionResult(string filename, string contentType)
  2. {
  3.     _stream = new FileStream(filename, FileMode.Open, FileAccess.Read);
  4.     _length = _stream.Length;
  5.     _contentType = contentType;
  6.  
  7.     byte[] hash;
  8.     using (var md5 = MD5.Create())
  9.     {
  10.         hash = md5.ComputeHash(_stream);
  11.     }
  12.     _etag = Convert.ToBase64String(hash);
  13.     _stream.Seek(0, SeekOrigin.Begin);
  14. }

Siplemente nos guardamos el stream al fichero y calculamos el MD5. Vamos a usar el MD5 como ETag del fichero.

Ahora, el siguiente paso es implementar el método ExecuteResult, que es donde se procesa toda la petición:

  1. public override void ExecuteResult(ControllerContext context)
  2. {
  3.     using (_stream)
  4.     {
  5.         var ranges = ParseRangeHeader(context.HttpContext.Request);
  6.         if (ranges == null || ranges.Empty)
  7.         {
  8.             new FileStreamResult(_stream, _contentType).ExecuteResult(context);
  9.             return;
  10.         }
  11.         if (ranges.Multiple)
  12.         {
  13.             ProcessMultipleRanges(context, ranges);
  14.         }
  15.         else
  16.         {
  17.             ProcessSingleRange(context, ranges.Values.Single());
  18.         }
  19.     }
  20. }

Básicamente:

  • Miramos el valor de la cabecera Range. Si no existe o no lo sabemos interpretar, creamos un FileStreamResult tradicional y lo ejecutamos. Es decir, nos saltamos todo lo de los rangos y devolvemos una descarga de fichero tradicional.
  • En caso de que haya más de un rango vamos a generar la respuesta multipart, con el método ProcessMultipleRanges.
  • En el caso de que haya un solo rango vamos a generar la respuesta con el método ProcessSingleRange.

El método ParseRangeHeader se encarga de parsear el valor de la cabecera Range:

  1. private Ranges ParseRangeHeader(HttpRequestBase request)
  2. {
  3.     var rangeHeader = request.Headers["Range"];
  4.     if (string.IsNullOrEmpty(rangeHeader)) return null;
  5.     rangeHeader = rangeHeader.Trim();
  6.  
  7.     if (!rangeHeader.StartsWith("bytes="))
  8.     {
  9.         return Ranges.InvalidUnit();
  10.     }
  11.  
  12.     return Ranges.FromBytesUnit(rangeHeader.Substring("bytes=".Length));
  13.  
  14. }

Se apoya en unas clases llamadas Ranges (que es básicamente una coleccion de objetos Range) y Range que representa un rango (origen, destino). El método importante de Ranges es el FromBytesUnit que es el que realmente parsea una cadena (sin el prefijo bytes=):

  1. public static Ranges FromBytesUnit(string value)
  2. {
  3.     var tokens = value.Split(',');
  4.  
  5.     var ranges = new Ranges();
  6.     foreach (var token in tokens)
  7.     {
  8.         ranges.Add(new Range(token));
  9.     }
  10.  
  11.     return ranges;
  12. }

Y la otra parte del trabajo la realiza el constructor de Range. Así si tenemos la cadena “0-100, 101-200” se llamará dos veces al constructor de Range pasándole primero la cadena “0-100” y luego “101-200”.

  1. public Range(string value)
  2. {
  3.     if (!value.Contains("-"))
  4.     {
  5.         _from = long.Parse(value);
  6.         _to = -1;
  7.     }
  8.     else if (value.StartsWith("-"))
  9.     {
  10.         _from = -1;
  11.         _to = long.Parse(value.Substring(1));
  12.     }
  13.     else
  14.     {
  15.         var idx = value.IndexOf('-');
  16.         _from = long.Parse(value.Substring(0, idx));
  17.         _to = idx == value.Length - 1 ? -1 :
  18.             long.Parse(value.Substring(idx + 1));
  19.     }
  20. }

Sirviendo rangos

Empecemos por el caso más sencillo: Tenemos un solo rango. En este caso el método ProcessSingleRange toma el control:

  1. private void ProcessSingleRange(ControllerContext context, Range range)
  2. {
  3.     var response = context.HttpContext.Response;
  4.     response.StatusCode = 206;
  5.     response.AddHeader("Content-Range", new ContentRange(range, _length).ToString());
  6.     response.AddHeader("ETag", _etag);
  7.     response.ContentType = _contentType;
  8.     FlushRangeDataInResponse(range, response);
  9. }

Este método es muy sencillo. Establece el código de respuesta (206) y crea las cabeceras Content-Range, ETag y Content-Type. Luego llama al método FlushRangeDataInResponse. Dicho método va leyendo los bytes del fichero y los va escribiendo en el buffer de respuesta. Para evitar cargar todo el rango en memoria del servidor (un rango puede ser todo lo largo que desee el cliente), los datos se van leyendo en bloques de 1024 bytes y se van escribiendo en el buffer de salida:

  1. private void FlushRangeDataInResponse(Range range, HttpResponseBase response)
  2. {
  3.     var creader = new ChunkReader(_stream);
  4.     ChunkResult result = null;
  5.     var startpos = 0;
  6.     do
  7.     {
  8.         result = creader.GetBytesChunk(range, startpos);
  9.         startpos += result.BytesRead;
  10.         if (result.BytesRead > 0)
  11.         {
  12.             response.OutputStream.Write(result.Data, 0, result.BytesRead);
  13.         }
  14.         response.Flush();
  15.     } while (result.MoreToRead);
  16. }

Aquí el que hace el trabajo de verdad es la clase ChunkReader. Esta clase es la que va leyendo un stream, por “trozos” y devuelve después de cada trozo si hay “más trozos por leer”:

  1. public class ChunkReader
  2. {
  3.     private readonly Stream _stream;
  4.     public ChunkReader(Stream stream)
  5.     {
  6.         _stream = stream;
  7.     }
  8.  
  9.  
  10.     public ChunkResult GetBytesChunk(Range range, int startpos)
  11.     {
  12.         var chunk = new ChunkResult();
  13.         var reader = new BinaryReader(_stream);
  14.         var remainingLen = range.Length != -1 ? range.Length - startpos : -1;
  15.         if (remainingLen == 0)
  16.         {
  17.             return new ChunkResult();
  18.         }
  19.                 
  20.         var bytesWanted = remainingLen != -1 ? Math.Min(1024, remainingLen) : 1024;
  21.         reader.BaseStream.Seek(range.FromBegin ? startpos : range.From + startpos, SeekOrigin.Begin);
  22.         var buffer = new byte[bytesWanted];
  23.         chunk.BytesRead = reader.Read(buffer, 0, (int)bytesWanted);
  24.         chunk.Data = buffer;
  25.         chunk.MoreToRead = remainingLen != -1
  26.             ? chunk.BytesRead != remainingLen
  27.             : chunk.BytesRead != 0;
  28.  
  29.         return chunk;
  30.     }
  31. }

El objeto ChunkResult contiene los bytes leídos, el número real de bytes leídos y un booleano que indica si hay “más datos” que leer.

Vayamos ahora al soporte para múltiples rangos. La idea es exactamente la misma, salvo que entre cada rango hay que generar el boundary correspondiente en la respuesta. Y eso es exactamente lo que hace el método ProcessMultipleRanges:

  1. private void ProcessMultipleRanges(ControllerContext context, Ranges ranges)
  2. {
  3.     var response = context.HttpContext.Response;
  4.     response.StatusCode = 206;
  5.     response.AddHeader("ETag", _etag);
  6.     response.AddHeader("Content-Type", "multipart/byteranges; boundary=THIS_STRING_SEPARATES");
  7.     foreach (var range in ranges.Values)
  8.     {
  9.         AddRangeInMultipartResponse(context, range);
  10.     }
  11. }

Primero añadimos los campos comunes (es decir el código de respuesta, el ETagm el content-type con el boundary). Y luego para cada rango llamamos al método AddRageInMultipartResponse, que simplemente coloca el boundary, luego el content-range y el content-type correspondiente y finalmente volca los datos del rango en el buffer de respuesta:

  1. private void AddRangeInMultipartResponse(ControllerContext context, Range range)
  2. {
  3.     var response = context.HttpContext.Response;
  4.     response.Write("-- THIS STRING SEPARATES\x0D\x0A");
  5.     response.Write(string.Format("Content-Type: {0}\x0D\x0A", _contentType));
  6.     var contentRange = new ContentRange(range, _length);
  7.     if (contentRange.IsValid)
  8.     {
  9.         response.Write("Content-Range: " + contentRange + "\x0D\x0A\x0D\x0A");
  10.     }
  11.  
  12.     FlushRangeDataInResponse(range, response);
  13.     response.Write("\x0D\x0A");
  14. }

¡Y ya estamos! Algunos ejemplos de lo que vemos. La imagen de la izquierda contiene un solo rango y la de la derecha dos:

imageimage

Con esto hemos visto como añadir soporte para rangos desde el servidor. Por supuesto no está del todo pulido, faltaría añadir el soporte para el If-Range pero bueno… los mimbres vendrían a ser eso.

Nota: Si lo pruebas y colocas un valor de Range inválido (p. ej. Range: bytes=100) recibirás un HTTP 400. Este 400 es generado por IIS incluso antes de que la petición llegue a ASP.NET MVC.

Saludos!

He subido todo el código del POST en un repositorio de GitHub: https://github.com/eiximenis/PartialDownloads. La aplicación web de demo simplemente tiene el siguiente código en Home/Index:

  1. public ActionResult Index()
  2. {
  3.     return this.RangeFile("~\\Content\\test.png", "image/png");
  4. }

Por lo tanto si navegas con un navegador a /Home/Index deberás ver o descargarte la imagen entera. Pero usando fiddler o cURL puedes generar una petición con rangos para ver como funciona.

Para usar cURL te basta con la opción --header:

curl --header "Range: bytes=0-100" http://localhost:39841/

Saludos!

con no comments
Archivado en:

Muy buenas! El objetivo de esta serie posts es ver como podemos implementar en ASP.NET MVC descargas de ficheros con soporte para “pausa y continuación”.

En este primer post veremos (por encima) que cabeceras HTTP están involucradas en las peticiones y las respuestas para permitir continuar una descarga.

Dicho soporte debe estar implementado en el cliente, pero también en el servidor. En el cliente porque ese tiene que efectuar lo que se llama una range request, es decir pasar en la petición HTTP que “parte” del archivo quiere. Y el servidor debe tener soporte para mandar solo esta parte.

En ASP.NET MVC usamos un FileActionResult para soportar descargas de archivos. Es muy cómodo y sencillo pero no tiene soporte para range requests. En esta serie posts veremos como podemos crearnos un ActionResult propio para que soporte este tipo de peticiones!

En el apartado range de la definición de HTTP1.1. se encuentra la definición de la cabecera Range que es la madre del cordero.

De hecho, básicamente hay dos cabeceras involucradas en una range request (que envía el cliente y que el servidor debe entender):

  1. Range: Especifica el rango que desea obtener el cliente
  2. If-Range: Especifica que se haga caso de “Range” solo si los datos no se han modificado desde xx (xx se especifica en la cabecera If-Range). Si los datos se han modificado, olvida Range y envía todos los datos.

Formato de Range

El formato de Range es muy sencillo. Oh sí, leyendo la especificación parece que es super complejo, pero para eso son las especificaciones… :P

A la práctica Range tiene el siguiente formato:

  • Range: bytes=x-y: El cliente quiere los bytes desde x hasta y (ambos inclusivos). P. ej. Range 0-499: Devuelve los 500 primeros bytes.

Si y no aparece entonces significa “dame desde el byte x hasta el final”:

  • Range: bytes=9000: El cliente quiere desde el byte 9000 (inclusive) hasta el finak

Si x no aparece entonces significa “dame los últimos y bytes”. P. ej:

  • Range: bytes=-500: El cliente quiere los últimos 500 bytes.

La cabecera admite distintos rangos separados por comas:

  • Range: bytes=100-200,400-500, –800: Dame los bytes del 100 al 200. Y del 400 al 500 y los últimos 800.

Si los rangos se solapan esto no debe generar un error:

  • Range: bytes=500-700, 601-999: El cliente quiere los bytes del 500 al 700 y del 601 al 999.

Nota: Que la cabecera Range empiece por bytes= no es superfluo. El estándard es extensible y permite que se puedan definir otras unidades además de bytes para especificar los rangos. De ahí que deba especificarse la unidad usada en la cabecera Range. De hecho el servidor puede usar la cabecera Accept-Ranges para especificar que unidades de rangos soporta (p. ej. Accept-Ranges: bytes). Nosotros nos centraremos única y exclusivamente en rangos de bytes.

Respuesta del servidor

Si el cliente solo pide un rango, la respuesta del servidor es una respuesta normal, salvo que en lugar de usar el código 200, devuelve un 206 (Partial content) y con la cabecera Content-Range añadida.

El formato de la cabecera Content-Range es el rango servido, seguido por la longitud total del elemento separado por /. P. ej:

  • Content-Range: bytes 100-200/5000 –> Se están sirviendo los bytes 100 a 200 (ambos inclusives) de un recurso cuya longitud es de 5000 bytes.
  • Content-Range: bytes 100-200/* –> Se están sirviendo los bytes 100 a 200 de un recurso cuya longitud es desconocida.

Sí, en Content-Range no hay el símbolo = entre bytes y el valor de rango. Mientras que en la cabecera Range si que existe dicho símbolo…

Por otra parte, si el cliente ha pedido más de un rango la respuesta del servidor pasa a ser una multipart, es decir, dado que el cliente nos envía varios rangos, en la respuesta debemos incluirlos todos por separado. Hay varias cosas a tener presente.

  1. El código de retorno no es 200, es 206 (Como en el caso anterior)
  2. El content-type debe establecerse a multipart/byteranges y debe indicarse cual es la cadena separadora (el boundary).
  3. Cada subrespuesta viene precedida del boundary y tiene su propio content-type y content-range indicados.

Un ejemplo sería como sigue (sacado de http://greenbytes.de/tech/webdav/draft-ietf-httpbis-p5-range-latest.html#status.206):

HTTP/1.1 206 Partial Content
Date: Wed, 15 Nov 1995 06:25:24 GMT
Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT
Content-Length: 1741
Content-Type: multipart/byteranges; boundary=THIS_STRING_SEPARATES

--THIS_STRING_SEPARATES
Content-Type: application/pdf
Content-Range: bytes 500-999/8000

...the first range...
--THIS_STRING_SEPARATES
Content-Type: application/pdf
Content-Range: bytes 7000-7999/8000

...the second range
--THIS_STRING_SEPARATES--

Si el servidor no puede satisfacer la petición del cliente debido a que los rangos pedidos son inválidos o hay demasiados que se solapan o lo que sea, puede devolver un HTTP 416 (Range not Satisfiable). Si lo hace deberia añadir una cabecera Content-Range inválida con el formato:

Content-Range: bytes */x (siendo x el número de bytes totales del recurso).

Otra opción si los rangos son inválidos es devolver el recurso entero usando un HTTP200 tradicional (muchos servidores hacen esto, ya que si un cliente usa rangos debe estar siempre preparado por si el servidor no los admite).

Cabecera If-Range

Nos falta mencionar la cabecera If-Range. La idea de dicha cabecera es que el cliente pueda decir algo como “Oye, tengo una parte del recurso descargado pero es de hace 3 días. Si no ha cambiado, pues me mandas los rangos que te indico en Range, en caso contrario, pues que le vamos a hacer me lo mandas todo con un 200 tradicional”.

El valor de If-Range puede ser, o bien un ETag que identifique el recurso o bien una fecha. Si no sabes lo que es un ETag, pues bueno es simplemente un identificador del contenido (o versión) del recurso. Puede ser un valor de hash, un valor de revisión, lo que sea. No hay un estándar definido, pero la idea es que si el cliente sabe el ETag de un recurso y lo manda, el servidor puede indicarle al cliente si dicho recurso ha cambiado o no. El cliente manda el ETag que tiene para dicho recurso (en la cabecera If-Range o bien en la If-None-Match si no hablamos de rangos).

Oh, por supuesto, uno podría crear un servidor que devolviese un ETag único cada vez que el cliente no le pasa un ETag previo y devolver siempre ETag recibido por el cliente en caso contrario. En este caso, se podría asegurar que cada cliente tendría un ETag distinto. ¿Ves por donde vamos, no? Un mecanismo sencillo y barato para distinguir usuarios únicos. Mucho mejor que todas esas maléficas cookies y además para usar ETags no es necesario colocar ningún popup ni nada parecido. Además, a diferencia de las cookies que se eliminan borrando las cookies (lo que mucha gente hace), los ETags se borran generalmente vaciando la cache del navegador (lo que hace mucha menos gente). Por supuesto, he dicho que uno podría hacer esto… no que se haya hecho :P

Bueno… Hasta ahí el primer post. Rollo teórico, pero bueno, siempre es importante entender como funcionan las cosas ¿no?… en el siguiente post pasaremos a la práctica!!! :D

con no comments
Archivado en:

Esta noche he tenido el placer de participar en el marco de un #programadorIO en un debate sobre los lenguajes tipados vs los no tipados. Puedes ver el debate en youtube: https://www.youtube.com/watch?v=sxOM6sYgn5U

Nota: En el contexto de este post “no tipado” significa débilmente tipado o de tipado dinámico. Y tipado significa fuertemente tipado o de tipado estático.

Mi opinión es que los lenguajes no tipados son muy adecuados para prototipados, por que las herramientas suelen ser más ágiles y porque te permiten “saltarte” en primera instancia una fase mucho más formal de diseño (fase que luego tarde o temprano tiene que venir, pero en un lenguaje tipado tiene que realizarse al principio para, precisamente, poder diseñar los tipos). De todos modos mi experiencia profesional versa mayoritariamente en los lenguajes tipados (C++, Java y C#). También he mencionado que creo que el auge de JavaScript no es tanto por el lenguaje en sí, si no que viene de la mano del auge del desarrollo web. Si desarrollas para la web, debes hacerlo casi si o si, en JavaScript. Hubiese estado bien la opinión de alguien que hubiese desarrollado tan solo en un lenguaje dinámico que no sea JavaScript (p. ej. Ruby) porque dentro de pequeñas diferencias creo que todos compartíamos mucho en común y que estábamos más del lado de los tipados que de los no tipados.

Bien, aclarado esto, yo he hecho bastante incapié, o lo he intentado al menos, en que a veces un sistema estático de tipos es un “corsé” no deseado y que para ciertas tareas un lenguaje no tipado es mejor o te permite realizarlas de forma mucho más natural o productiva. Y voy a poner algunos ejemplos concretos usando C# (casi todo lo que diré es aplicable a Java también).

Ejemplo 1: Deserialización de datos dinámicos

Este es el ejemplo que apuntaba Pedro. En el fondo es muy simple, puesto que si los datos son dinámicos ¿qué mejor que un lenguaje dinámico para deserializarlos?

Imagina que tienes que consumir una api REST que te puede devolver un objeto (da igual el formato, JSON, XML o lo que sea) que tiene 150 campos posibles, todos ellos opcionales. Pueden aparecer o no pueden aparecer. Si tienes que deserializarlo en un lenguaje tipado, que haces: crear una clase con 150 miembros? Y si alguno de los miembros es un int y vale 0… este 0 es porque no ha aparecido o bien porque realmente he llegado un 0. Si claro, puedes usar Nullable<int> pero… bonito y divertido no es.

¿Y si en lugar de ser campos simples son compuestos? ¡Terminas teniendo una jerarquía enorme de clases tan solo para deserializar las respuestas!

El mismo problema te lo encuentras si eres el que crea la API REST por supuesto. Pero incluso peor… porque igual no puedes usar la clase con 150 miembros porque a lo mejor los miembros vacíos o con el valor por defecto se serializarían también y no quieres eso. Vas a terminar igual: con un numero enorme de clases tan solo para serializar los datos.

Si los datos con los que trabajas tienen una naturaleza dinámica, un lenguaje dinámico es lo mejor para tratarlos.

Por supuesto podrías trabajar con algo parecido a un Dictionary<string, object> y serializar el diccionario con el formato de datos esperado. Si, pero haciendo esto estás haciendo un workaround, te estás enfrentando al sistema de tipos. Estás simulando un tipo dinámico en un lenguaje estático. Todas las ventajas del tipado estático desaparecen (el compilador no te ayudará si te equivocas en el nombre de una clase), las herramientas de refactoring no pueden ayudarte en nada (incluso menos que en el caso de un lenguaje dinámico), tu código queda “sucio” (lleno de dictionarios, llamadas a métodos .Add) y además… tardas más.

Ejemplo 2: Jerarquías de clases distintas autogeneradas

Imagina que tienes dos servicios WCF distintos que te devuelven datos muy parecidos. En ambos casos son datos de productos. En ambos casos siempre hay un nombre, un precio y un id. Los nombres de los campos y los tipos SOAP asociados son los mismos (imagina que eso lo puedes definir o imponer).

Si generas los proxies para acceder a los servicios vas a terminar con dos clases diferentes (una por cada servicio). Pero incluso aunque los miembros para el nombre, precio e id se llamasen igual no podrías intercambiar esos proxies en código. Vas a tener dos clases iguales (ambas tendrán un string nombre, un decimal precio y un int id) pero para el compilador son dos clases distintas. Cualquier función que opere sobre uno de los proxies no puede operar con el otro. A pesar de que el aspecto de ambas clases es “idéntico”, a pesar de que representan el “mismo” concepto, para el compilador tienen la mismo parecido que el de un perro con una manzana.

Sí: el problema principal está en que tienes dos clases distintas para lo mismo, pero eso ocurre en la vida real cuando hay herramientas que autogeneran código. ¿Qué soluciones tienes? Pues crear una tercera clase que sea tu “producto” y “transformar” cada uno de los objetos proxy a un objeto de tu clase “producto” que será con la que trabaje tu código. Sí, es posible que los lenguajes tipados tengan un rendimiento superior a los no tipados, pero si empiezas a tener que copiar objetos muchas veces…

¿No estaría bien que tu código que trabaja con un producto pudiese trabajar directamente con cualquiera de los dos proxies? Aunque sean de clases “distintas”. Aunque no haya ninguna interfaz en común. A fin de cuenta tu código tan solo necesita un nombre, un id y un precio. Estaría bien que pudiese funcionar con cualquier objeto que tiene esos tres campos no? Eso se llama duck typing y viene “de serie” con los lenguajes no tipados.

¡Ojo! Que el hecho de que un lenguaje sea tipado no le impide tener algo muy parecido (a efectos prácticos idéntico) al duck typing: P. ej. este problema de los proxies se podría solucionar en C++ con el uso de templates. El uso de templates en C++ es un ejemplo de lo que conoce como structural typing (que es, básicamente, duck typing en tiempo de compilación). Pero no, ni Java ni C# tienen soporte para structural typing.

Ejemplo 3: Generics

Podría poner muchos ejemplos parecidos al de los proxies, incluso cuando no hay código generado. Un ejemplo rápido. Tengo cuatro clases mías, independientes entre ellas.

Ahora quiero crear una colección propia, que implemente IEnumerable<T> pero que internamente use un diccionario, ya que continuamente se estarán buscando elementos por nombre.

Por supuesto las 4 clases tienen una propiedad string Name para guardar el nombre.

Pues bien, para crear esas cuatro colecciones, tienes dos opciones:

  1. Crearte cuatro clases colección idénticas que solo cambian el tipo de datos que aceptan / devuelven. Si, eso suena muy .NET 1.0
  2. Usar generics… Salvo que no puedes.

Y no puedes usar generics porque dentro del código de la clase genérica no puedes acceder a la propiedad Name del tipo genérico. Porque el tipo genérico es “object” por defecto. Por supuesto si las 4 clases implementasen una interfaz común, que se llamase INamedItem (p. ej.) y que definiese la propiedad Name, podrías poner una restricción de generics para que el tipo genérico implementase INamedItem y entonces podrías usar generics para crear tu colección propia. Pero realmente la interfaz INamedItem no representa ningún concepto real. Está tan solo para permitirte usar generics en este caso. Este es otro caso donde duck typing vendría bien: tu colección propia debería funcionar con cualquier objeto que tenga la propiedad Name. Pero el sistema de tipos de C# (con el de Java pasa lo mismo) es incapaz de dar soporte a esta situación.

Ejemplo 4: delegados

Tengo una función que devuelve un bool y acepta un int. Tengo un delegado de tipo Func<int, bool> que “apunta” a dicha función.

Quiero pasar este delegado a otra función… que espera un Predicate<int>.

Conceptualmente Func<T, bool> es lo mismo que Predicate<T> pero para el compilador son totalmente distintos. Por suerte en este caso la solución es muy sencilla, convertir un Func a un Predicate es muy sencillo, pero tienes que hacerlo igualmente.

Nota: Por cierto, aprovechando, no uses nunca Predicate<T> en tu código. Está obsoleto. Func<T,bool> es lo que se debe usar.

Ejemplo 5: Instanciación de tipos dinámica

Este es muy sencillo: quieres instanciar un tipo cuyo nombre no conoces en tiempo de compilación. Da igual la razón: el método puede venirte de un fichero, BBDD o lo que sea.

Cierto, en C# y en Java puedes usar reflection (p. ej. Activator.CreateInstance en C#) para crear la instancia. El problema es que usar reflection elimina todas las ventajas del tipado estático y además el código queda muy “sucio”. Pasar de reflection a tipado estático otra vez no siempre es posible (si sabes que cualquiera de las posibles clases implementa el mismo interfaz puedes convertir el resultado al interfaz y a partir de allí recuperar el tipado estático). Y si tienes que hacer varias cosas usando reflection el código queda “sucio”, dificil de entender y ninguna herramienta de refactorización puede ayudarte.

En un lenguaje no tipado en cambio, la creación del objeto puede requerir una sintaxis distinta pero una vez creado el objeto invocar los métodos será con la misma sintaxis de siempre.

En resumen

Todos los ejemplos presentados (y hay más de posibles), se resumen en dos grandes tipos: comportamiento dinámico (ejemplos 1 y 5) y “objetos semánticamente compatibles pero incompatibles a la práctica” (el resto de ejemplos).

¿Justifican esos casos usar un lenguaje dinámico para todos tus proyectos? No. Pero si que justifican que los lenguajes estáticos añadan opciones para facilitar la programación dinámica. Por ejemplo el dynamic de C# es un paso en esa dirección. Los templates de C++ son otro (los genérics de .NET o de Java ni de lejos).

Entonces… ¿tienes que usar un lenguaje estático para todos tus proyectos? Pues no. Puedes hacer grandes proyectos tanto en lenguajes tipados como en no tipados. Y puedes hacer aberraciones en ambos.

Aunque yo personalmente prefiero un lenguaje estático a uno dinámico, me siento cómodo en estos y a veces cuando estoy en C# si que me gustaría tener toda la flexibilidad que estos me ofrecen. De hecho, cuanto más me he acostrumbrado a JavaScript más echo en falta ciertas cosas en C#. Pero no siempre, no continuamente. Solo “cuando yo quiero”.

Ahora sí, lo que tengo claro es que desarrollar bien en un lenguaje no tipado requiere mayor disciplina que en un lenguaje tipado y que cualquier desarrollador que se precie debería conocer los conceptos de orientación a objetos clásicos (de hecho yo creo que cualquier desarrollador debería aprender C++, pero esa es otra batalla :P).

Bueno… si has llegado hasta aquí… gracias por leer este tostón! Y por supuesto, siéntete libre de dejar un comentario con tu opinión!

Un saludo!

con no comments
Archivado en:

Una de las cosas que se argumentan en contra de JavaScript cuando se habla de orientación a objetos es que no soporta la visibilidad de métodos o propiedades. Es decir, todo es público por defecto.

Mucha gente hoy en día cuando programa en JavaScript adopta alguna convención tipo “lo que empiece por guión bajo es privado y no debe ser invocado”. Como chapuza para ir tirando, pues bueno, pero en JavaScript hay maneras de simular una visibilidad privada y de que realmente el creador de un objeto no pueda invocar algunos métodos. En este post veremos un método rápido y sencillo. Por supuesto no es el único ni tiene porque ser el mejor…

Empecemos por la declaración de una función constructora que me permite crear objetos de tipo Foo:

  1. var Foo = function () {
  2.     this.count = 0;
  3.     this.inc = function() {
  4.         this._addToCount(1);
  5.     };
  6.  
  7.     this._addToCount = function (a) {
  8.         this.count += a;
  9.     };
  10. }
  11.  
  12. var foo = new Foo();
  13. console.log(foo.count);
  14. foo.inc();
  15. // Esto debera ser privado
  16. foo._addToCount(100);
  17. console.log(foo.count);
  18. // count no debera poder accederse
  19. foo.count = 10;
  20. console.log(foo.count);

Estoy usando la convención de que los métodos privados empiezan por un guión bajo. Pero es esto: una convención. Para el lenguaje no hay diferencia. De hecho si ejecuto este código el resultado es el siguiente:

image

El desarrollador que crea un objeto Foo puede acceder tanto a inc, como a addToCount como a count. Como podemos solucionar eso?

La solución pasa por no devolver a quien crea el objeto Foo entero si no un “subobjeto” que tan solo contenga las funciones publicas:

  1. var Foo = function () {
  2.     this.count = 0;
  3.     this.inc = function() {
  4.         this._addToCount(1);
  5.     };
  6.  
  7.     this._addToCount = function (a) {
  8.         this.count += a;
  9.     };
  10.  
  11.     return {
  12.         inc : this.inc
  13.     };
  14. }
  15.  
  16. var foo = new Foo();
  17. console.log(foo);

Si ejecuto este código parece que vamos por el buen camino:

image

Ahora el objeto foo contiene tan solo el método inc. Pero, que ocurre si ¿lo ejecutamos? Pues eso:

image

JavaScript se queja que el método _addToCount no está definido! Que es lo que ha ocurrido? Lo ocurrido tiene que ver con el contexto de JavaScript o el valor de this. El método inc que invocamos es el método inc del objeto anónimo que devolvemos al final de la función constructora de Foo. Dentro de este método el valor de this es el valor del objeto anónimo que, por supuesto, no tiene definido _addToCount. Parece que estamos en un callejón sin salida, verdad?

Aquí es cuando entra en escena la función bind: bind es un función que se llama sobre una función. El resultado de aplicar bind a una función es otra función pero atada permanentemente al contexto que se pasa como parámetro a bind. Dicho de otra manera cuando devolvemos el objeto anónimo, tenemos que modificar el contexto del método inc para que sea el objeto Foo entero. Así modificamos el return para que quede como:

  1. return {
  2.     inc: this.inc.bind(this)
  3. };

Cuando se ejecuta este return el valor the this es el objeto Foo entero así que lo que estamos devolviendo es un objeto anónimo, con una función inc (que realmente es this.inc es decir la función inc del objeto Foo entero), pero que está bindeada a this (el objeto Foo entero), es decir que cuando se ejecute este método inc del objeto anónimo el valor de this no será el objeto anónimo si no el propio objeto Foo.

Con esto hemos terminado! Ahora cuando llamamos a new Foo(), lo que obtenemos es un objeto solo con el método inc. Cuando invocamos inc todo funciona ahora correctamente. Y ya no podemos invocar el método privado _addToCount ni acceder a la propiedad count.

Esto es tan solo un mecanismo, hay varias maneras distintas de hacer lo mismo pero todas se basan en este mismo principio.

Saludos!

PD: Os dejo el código de un método, que he llamado _publicInterface. Dicho método lo que hace es, a partir de un objeto, crear otro objeto que contenga tan solo aquellas funciones que NO empiezan por guión bajo:

  1. this._publicInterface = function() {
  2.     var keys = Object.keys(this);
  3.     var protocol = {};
  4.     for (var idx = 0; idx < keys.length; idx++) {
  5.         var key = keys[idx];
  6.         if (key.charAt(0) !== '_' && typeof (this[key]) === "function") {
  7.             protocol[key] = this[key].bind(this);
  8.         }
  9.     }
  10.  
  11.     return protocol;
  12. };

Así podéis definir en vuestros objetos funciones públicas y privadas (que empiecen por guión bajo) y en el return final hacer: return this._publicInterface();

con no comments
Archivado en:

Muy buenas! Estreno el blog este 2014… dios a finales de Febrero! A ver, si empezamos a retomar el ritmo…

Este es un post sencillito, por si os encontráis con ello. La situación es la siguiente: Tenéis controladores que devuelven vistas parciales, las cuales desde JavaScript incluís dentro de vuestro DOM a través de una llamada Ajax, usando p. ej. el método load de jQuery.

Todo funciona correctamente, hasta que un día el usuario entra en el site, se va a comer y cuando vuelve pulsa uno de esos enlaces (o botones o lo que sea) que incluyen una de esas llamadas Ajax… Y ocurre que en lugar de aparecer la vista parcial, aparece la página de Login allí incrustada.

La razón, obviamente, es que la acción que devuelve la vista parcial está protegida con [Authorize] y al haber caducado la cookie de autorización, este atributo manda un HTTP 401 (no autorizado). Hasta ahí bien. Entonces entra en juego nuestro amigo FormsAuthentication, que “captura” este 401 y lo convierte en un 302 (redirección) que es lo que recibe el navegador. Desde el punto de vista del navegador, lo que llega es un 302 por lo que este, obendientemente, se redirige a la página de Login. Las peticiones Ajax hacen caso del HTTP 302 y por lo tanto el resultado de la redirección (la página de Login) se muestra.

Una alternativa sencilla y rápida para solucionar esto consiste en modificar la petición modificada por FormsAuthentication, de forma que cambiamos todos los 302 que sean resultado de una petición Ajax por un 401 y así revertir lo que FormsAuthentication hace.

  1. protected void Application_EndRequest()
  2. {
  3.     var context = new HttpContextWrapper(this.Context);
  4.     if (FormsAuthentication.IsEnabled && context.Response.StatusCode == 302
  5.         && context.Request.IsAjaxRequest())
  6.     {
  7.         context.Response.Clear();
  8.         context.Response.StatusCode = 401;
  9.     }
  10. }

Con esto convertimos todos los 302 en 401 cuando sean peticiones Ajax y estemos bajo FormsAuthentication. Ojo, que los convertimos todos, incluso aquellos 302 legítimos que podrían haber.

Ahora ya solo queda actualizar nuestro código JavaScript y comprobar que no recibimos un 402 ;)

Postdata 1: .NET Framework 4.5

Si usas .NET Framework 4.5 (VS2012), ya no es necesario que hagas este truco. En su lugar puedes usar la propiedad SuppressFormsAuthenticationRedirect de HttpResponse y ponerla a true. Si el valor de esa propiedad es true, pues FormsAuthentication no convierte los 401 en 302.

Es una propiedad que debes establecer cada vez, por lo que lo puedes hacer de nuevo en el Application_EndRequest de Global.asax si lo deseas.

Si alguien me pregunta porque narices esa propiedad es a nivel de Response (ya me dirás tu porque el objeto Response tiene que “entender” del framework de autorización), pues no lo sé… pero no me termina de gustar, la verdad.

Postdata 2: Katana Cookie Middleware

Ya lo decía el bueno de Andrés Montes: La vida puede ser maravillosa. Si en FormsAuthentication arreglaron esta situación con la propiedad SuppressFormsAuthenticationRequest en el middleware de autenticación por cookies de Katana volvemos a la situación anterior. Y si usas VS2013 o los nuevos templates de ASP.NET en VS2012 no estarás usando FormsAuthentication si no el middleware de Katana.

Por suerte Katana está mejor pensado que FormsAuthentication y podemos configurar mucho mejor el middleware de autenticación basada en cookies.

Buscad donde se configura el middleware basado en cookies de Katana, que por defecto es en el fichero App_Start/StartupAuth.cs y sustutís:

  1. app.UseCookieAuthentication(new CookieAuthenticationOptions
  2. {
  3.     AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
  4.     LoginPath = new PathString("/Account/Login")
  5. });

por:

  1. app.UseCookieAuthentication(new CookieAuthenticationOptions
  2. {
  3.     AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
  4.     LoginPath = new PathString("/Account/Login"),
  5.     Provider = new CookieAuthenticationProvider
  6.     {
  7.         OnApplyRedirect = ctx =>
  8.         {
  9.             if (!IsAjaxRequest(ctx.Request))
  10.             {
  11.                 ctx.Response.Redirect(ctx.RedirectUri);
  12.             }
  13.         }
  14.     }
  15. });

De esta manera tomamos el control del redirect por 401 y lo hacemos solo si la request no es Ajax. Bueno, bonito y barato.

Ah si! El método IsAjaxResponse… Este método no es el método IsAjaxResponse clásico (ctx.Request es una IOwinRequest) así que os lo tendréis que crear vosotros. Aquí os pongo una implementación:

  1. public static bool IsAjaxRequest(IOwinRequest request)
  2. {
  3.     IReadableStringCollection query = request.Query;
  4.     if (query != null)
  5.     {
  6.         if (query["X-Requested-With"] == "XMLHttpRequest")
  7.         {
  8.             return true;
  9.         }
  10.     }
  11.  
  12.     IHeaderDictionary headers = request.Headers;
  13.     if (headers != null)
  14.     {
  15.         if (headers["X-Requested-With"] == "XMLHttpRequest")
  16.         {
  17.             return true;
  18.         }
  19.     }
  20.     return false;
  21. }

No,  no me deis las gracias… Si el método IsAjaxRequest os funciona las dais al equipo de Katana, ya que está copiado del código fuente de Katana (concretamente de aquí). Si, si… yo también me pregunto porque es privado este método y no un método de extensión público.

En fin… eso es todo! Espero que os sea útil!

Saludos!

Fuentes usadas:

con no comments
Archivado en: ,
Más artículos Página siguiente >