De NuGet y la gestión de paquetes

Ya hace bastante tiempo que NuGet salió y desde entonces se ha convertido en un compañero inseparable de todos nosotros. Y más que va a serlo cuando vNext salga de forma definitiva. En este post doy por supuesto que conoces NuGet y que lo has usado alguna vez (si no… ¡debes aprender a usarlo ya!). En este post quiero comentar los tres modos de funcionamiento que tiene NuGet y algunas cosillas más con las que me he encontrado.

Funcione NuGet en el modo en que funcione, cuando agregamos un paquete siempre ocurre lo mismo:

  • Se crea (si no existe) un directorio packages a nivel de la solución (localizado en el mismo directorio que el .sln).
  • Se descarga el paquete en dicho directorio
  • Se crea (si no existe) un fichero packages.config en el proyecto al cual se haya agregado el paquete y se añade una línea indicando el paquete agregado.
  • Se añade una referencia en el proyecto que apunta al ensamblado del paquete que se encuentra en el directorio packages. Y si el paquete tiene scripts de instalación adicionales, pues se ejecutan.

Recuerda que NuGet es básicamente un automatizador de agregar referencias en VS. Hace todo aquello que harías tu manualmente (descargar el ensamblado, guardarlo en algún sitio, agregar la referencia y cosas extra como editar web.config) y nada más (o nada menos, depende de como se mire).

Vamos a configurar NuGet para que funcione en el primero de los modos. Para ello abre VS2013 y en Tools –> Options –> NuGet Package Manager desmarca las dos checkboxes que están bajo el título de “Package Restore”. De esta manera NuGet funciona de la forma en que funcionaba originalmente. Y dicha forma consiste en que NuGet no hará nada más que lo que hemos descrito hasta ahora. Eso significa que cuando subas a tu repositorio de control de código fuente el proyecto debes incluir el directorio packages que contiene los binarios de los paquetes instalados por NuGet. Si no lo haces, cuando otra persona quiera descargarse el código fuente el proyecto no le compilará porque no encontrará las referencias a los paquetes NuGet:

image

¿Está todo perdido? Pues no, porque NuGet detectará que hay paquetes referenciados (eso lo sabe mirando el packages.config) que no existen en el directorio packages. Así mostrará el siguiente mensaje en la “Package Manager Console”:

image

Este mensaje aparece porque estamos en una versión nueva de NuGet. Cuando apareció NuGet este mensaje no aparecía y honestamente no sé cual era la solución entonces porque ese era un escenario no soportado: en la primera versión de NuGet los paquetes descargados debían subirse al control de código fuente.

Una versión de NuGet posterior habilitó el segundo modo de funcionamiento de NuGet. Para activarlo se debe pulsar sobre la solución en el “solution explorer” y seleccionar la opción “Enable NuGet Package Restore”:

image

Cuando se pulsa esta opción se crea una carpeta .nuget en la raíz de la solución que contiene tres ficheros (NuGet.config, NuGet.exe y NuGet.targets). Y además se modificarán los ficheros de la solución para agregarles por un lado una entrada nueva dentro de <PropertyGroup>:

  1. <RestorePackages>true</RestorePackages>

Y la ejecución de la tarea para que NuGet descargue los paquetes al compilar la solución:

  1. <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
  2. <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
  3.   <PropertyGroup>
  4.     <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
  5.   </PropertyGroup>
  6.   <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
  7. </Target>

De este modo al compilar la solución NuGet se descargará los paquetes que no existan de forma automática. Habilitar esta opción te marcará automáticamente la primera de las checkboxes que habíamos desmarcado antes en Tools –> Options –> NuGet Package Manager.

Así, ahora, una de las preguntas más recurrentes sobre NuGet (¿Tengo que subir los paquetes a mi sistema de control de código fuente?) se respondía ahora diciendo que si los querías subir podías hacerlo sin problemas pero que si no, no era necesario siempre y cuando la solución tuviese habilitada la opción de “Package Restore”. En este último caso la carpeta .nuget si que debías subirla.

En principio con esos dos modos cubrimos la totalidad de los escenarios pero con la versión 2.7 de NuGet agregaron un tercer modo de funcionamiento. Dicho tercer modo es básicamente el modo anterior que acabamos de describir pero automatizado. Ya no tenemos que hacer nada, excepto marcar la segunda de las checkboxes que hemos desmarcado al principio (y es que este es, a partir de la versión 2.7, el modo por defecto de NuGet).

Para verla en acción marca la segunda checkbox, y luego borra la carpeta .nuget. Luego recompila la solución y recibirás un error: “This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is XXX\.nuget\Nuget.targets”. Este error se da porque a pesar de que hemos borrado la carpeta .nuget, tenemos los proyectos todavía configurados para que la usen. Así, que no toca otra: abrir en modo texto los ficheros de proyecto y eliminar las líneas que se nos añadieron antes.

Una vez hecho esto, recargas los proyectos y al recompilar automáticamente NuGet descargará los paquetes. La ventaja de este modo de funcionamiento respecto al anterior es que no es intrusivo: no requiere modificar los ficheros de proyecto.

Si actualmente usas NuGet 2.7 o superior (que es de esperar que sí) y tienes la carpeta .nuget en tu repositorio de código fuente lo mejor que puedes hacer es eliminarla. Y luego modificar los proyectos para quitar las líneas indicadas anteriormente. Y con la segunda checkbox marcada (que es como está por defecto) ya tienes la descarga de paquetes automatizada. Por si tienes alguna duda sobre lo que tienes que eliminar en los ficheros de proyecto (aunque son las líneas mencionadas antes), todo el proceso está documentado en la propia web de NuGet. Si usas team build también son necesarias pequeñas modificaciones en TFS2012 o anterior (en TFS2013 así como Visual Studio Online o bien deploys en Azure web sites el proceso está ya integrado). De nuevo tienes toda la documentación en la web de NuGet sobre como configurar el team build.

De todos modos que tengas habilitada la descarga de paquetes automatizada no te impide colocar los paquetes (la carpeta packages) en el repositorio de control de código fuente: es una opción personal. Colocarlos en el sistema de control de código fuente te evita una dependencia con el propio NuGet (que aunque se cae pocas veces, a veces lo hace). Hace tiempo Juanma escribió en su blog un post sobre los peligros de depender del gestor de paquetes. Hay soluciones más elaboradas como no tener los paquetes en el control de código fuente pero usar un servidor de NuGet corporativo. Aquí ya, cada caso es un mundo.

Proyectos en varias soluciones

Vale, eso es un poco más frustrante y es un aviso más que otra cosa: si tienes un proyecto con paquetes gestionados por NuGet y este proyecto lo tienes en varias soluciones, asegúrate de que todas las soluciones (los ficheros .sln) están en el mismo directorio. En caso contrario puedes tener problemas. Es lógico una vez se entiende que hace NuGet y realmente es difícil que pueda hacer otra cosa que la que hace, así que bueno… es algo a tener en cuenta.

Vamos a reproducirlo paso a paso, para entender que ocurre. Para ello crea un directorio, yo lo he llamado nuroot. Luego crea otra carpeta (yo la he llamado folder1) dentro de nuroot:

image

Ahora crea una solución de VS (una aplicación de consola) dentro de folder1 (yo la he llamado DemoProject). Una vez hecho esto agrega un paquete de NuGet a la solución (p. ej. DotNetZip). Ahora la estructura de paquete debe ser como sigue:

image

El directorio packages está al nivel de la solución, y si miras en el proyecto verás que la referencia al ensamblado (en el caso de DotNetZip el ensamblado se llama Ionic.Zip) apunta al directorio packages. De hecho la referencia se guarda relativa al fichero de proyecto (si abres el .csproj en modo texto lo verás):

  1. <Reference Include="Ionic.Zip">
  2.   <HintPath>..\packages\DotNetZip.1.9.3\lib\net20\Ionic.Zip.dll</HintPath>
  3. </Reference>

Perfecto. Ahora crea otra solución vacía (New Project –> Other Project Types –> Visual Studio Solution –> Empty Solution) y dale el nombre que quieras. Yo la he llamado SecondSolution. Lo importante es que la crees en nuroot, no en folder1. Por defecto VS crea un directorio para la solución, pero vamos a eliminarlo. Ve a nuroot\SecondSolution y mueve el fichero SecondSolution.sln a nuroot. Luego borra el directorio SecondSoution. En este punto la estructura de directorios es pues la misma de antes, con la salvedad de que en la carpeta nuroot hay el fichero SecondSolution.sln.

Finalmente agrega el proyecto existente (DemoProject) a la solución SecondSolution. ¡Una vez cargues el proyecto verás el mensaje de que faltan paquetes de NuGet! Dale a Restore para que NuGet se descargue los paquetes faltantes y la estructura de directorios será la siguiente:

image

Ten presente que NuGet funciona a nivel de solución. Cuando hemos agregado el proyecto DemoProject a la segunda solución, NuGet ha examinado el fichero packages.config del proyecto y ha visto una referencia a DotNetZip. Luego ha examinado el directorio packages de la solución. Y al estar esta otra solución en otro directorio que la anterior, NuGet no encuentra el directorio packages, y por lo tanto asume que debe descargarse los paquetes. Y al descargarlos es cuando nos aparece el otro directorio packages ahora colgando de nuroot (el directorio donde tenemos SecondSolution.sln).

Si compilas el proyecto todo funcionará pero hay un tema importante ahí. El proyecto DemoProject ya contenía una referencia al ensamblado de DotNetZip y al existir dicha referencia NuGet no la modificará. Es decir, la referencia sigue apuntando donde apuntaba inicialmente (nuroot\folder1\DemoProject\Packages).

Cierra VS y borra los dos directorios packages. Con esto simulas lo que le ocurriría a alguien que se descargase el código fuente (suponiendo que los paquetes no están subidos en él). Ahora carga SecondSolution.sln otra vez y de nuevo verás que NuGet dice que faltan paquetes (obvio, pues los hemos borrado todos). Restaura de nuevo y verás como NuGet crea otra vez el directorio nuroot\packages. Pero el fichero csproj sigue teniendo la referencia a nuroot\folder1\DemoProject y por lo tanto no encontrará la referencia. Es decir, el código no compilará. Para que te compile debes abirlo con la solución DemoProject.sln, restaurar paquetes (o compilar simplemente, recuerda que al compilar se restauran automáticamente) y entonces ya te compilará (desde ambas soluciones).

En este escenario quizá no te parezca tan grave porque total, haces un readme.txt y que diga “Abrir primero DemoProject.sln” y listos. Hombre, es feo y un poco chapuza pero bueno…

Pero el problema lo tienes si luego añades otro paquete de NuGet al proyecto cuando lo tienes abierto con la solución SecondSolution.sln. P. ej. yo he instalado el CommandLineParser. Por supuesto este paquete está instalado en nuroot\packages y la referencia del proyecto apunta a este directorio. Observa como han quedado las referencias del proyecto:

  1. <Reference Include="CommandLine">
  2.   <HintPath>..\..\..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll</HintPath>
  3. </Reference>
  4. <Reference Include="Ionic.Zip">
  5.   <HintPath>..\packages\DotNetZip.1.9.3\lib\net20\Ionic.Zip.dll</HintPath>
  6. </Reference>

Por lo tanto ahora si borras los directorios packages, debes abrir el proyecto en ambas soluciones y compilarlas en ambas (para forzar la restauración de paquetes). La primera solución que compiles te dará un error (le faltará el paquete que se añadió a través de la otra solución), da igual el orden en que lo hagas. La segunda que compiles si que compilará bien.

Al final terminarás con ambos paquetes instalados en ambos directorios packages pero solo se usará uno de cada (el referenciado por el proyecto).

Igual no te parece muy grave pero si tienes una build que compila una de esas soluciones dala por perdida: cada vez que se ejecuta la build se parte d un entorno nuevo, por lo que la build no compilará, está rota.

La mejor solución para ello es simplemente tenerlo presente: evita que un mismo proyecto esté en varias soluciones localizadas en directorios distintos (si las soluciones, los ficheros .sln, están todos en la misma carpeta no hay problema porque el directorio packages de todas ellas es el mismo).

Espero que el post os haya resultado interesante!

Saludos!

Publicado por Eduard Tomàs i Avellana con 2 comment(s)
Archivado en:

ASP.NET Historia de una optimización. ¡Cuidado con la Sesión!

En un cliente en el que he estado últimamente tenían un problema de rendimiento en su aplicación ASP.NET. El problema era más o menos que “cuando un usuario está buscando algo, entonces la aplicación se queda bloqueada”. Por supuesto el primer paso fue determinar que significaba “bloqueada” ya que es una palabra un tanto ambigua… Uno de los aspectos que conoce todo el mundo que trata con problemas reportados por usuarios finales es que muchas veces (por no decir casi siempre) el problema está descrito entre mal y peor.

Al final que la aplicación se bloqueaba significaba que el usuario no podía navegar a ningún otro sitio. Debo contextualizar que es una aplicación web que consta de una pagina principal con un menú a modo de “escritorio” y cada opción que selecciona el usuario se abre en una ventana nueva de navegador. Así los usuarios terminan teniendo varias ventanas del navegador abiertas y van haciendo cosillas (búsquedas, ediciones, lo que sea que hagan) en cada una de ellas. Pues bien lo que ocurría es que mientras en alguna ventana se estaban buscando datos, cuando se pulsaba en cualquier otra opción, esta se quedaba esperando y no cargaba hasta que finalizaba la búsqueda de la otra ventana.

Lo primero que hice fue verificar que no estuviesen haciendo ellos un bloqueo por código (tenían algunos singletons donde se guardaban ciertos datos) pero no vi nada sospechoso. Había descartado cualquier bloqueo de BBDD porque un análisis previo realizado había confirmado que no habían bloqueos, por lo que en este aspecto estaba tranquilo. El siguiente punto fue ver en que momento se bloqueaban las otras peticiones. Ahí tuve que invertir un poco de tiempo ya que el proyecto consta de varias soluciones de VS y es un proyecto en webforms bastante complejo. Al final pude configurar mi sistema para depurar parte de la web y observé que ni llegaba al Page_Load del formulario. Eso era interesante pero como el ciclo de vida de Webforms es inescrutable tenía que asegurarme que no se quedase en algún punto anterior al Page_Load (lease algún evento Init o PreInit perdidos por ahí) de cualquier formulario base que pudiese haber. Después de navegar un poco por el código fuente (es una aplicación con una jerarquía de formularios bastante… interesante) llegué a la conclusión de que no. De que las peticiones ni tan siquiera habían entrado en el pipeline de webforms. Se quedaban atascadas antes.

Finalmente me dio por monitorizar el estado de peticiones del proceso de trabajo desde la consola de IIS y llegué a ver lo siguiente:

Captura

Tenía dos peticiones en marcha y una tercera que estaba bloqueada esperando acceso a la sesión. Y es que en asp.net solo se permite una petición concurrente por usuario que tenga acceso de escritura a la sesión. Si la petición requiere solo de acceso a lectura no hay problema, pero nunca entraran dos peticiones concurrentes que tengan permisos de escritura en la sesión (por usuario). Esto es muy importante porque no se trata de si realmente se lee o se escribe en la sesión. Se trata de si se tienen permisos para escribir o leer en la sesión.

De las dos peticiones en marcha que tenía una era a un servicio web que no usaba sesión y la otra era la del buscador. El problema básico de la aplicación es que todas las peticiones tenían permisos de lectura y escritura en la sesión, por lo que todas las peticiones se encolaban una tras otra y nunca había dos peticiones (a dos formularios .aspx) en paralelo. Eso en la operativa normal de la aplicación no se notaba y pasaba desapercibido, pero cuando había un buscador en marcha se notaba simplemente porque el buscador podía tardar bastante tiempo en responder (del orden de segundos). Por lo tanto si un usuario mientras esperaba que el buscador le devolviese resultados (recuerda que el buscador está en otra ventana) volvía a la ventana principal y intentaba lanzar otra operación, esta operación se quedaba sin poder cargarse hasta que finalizaba el buscador. Además al abrirse en ventana nueva, la sensación del usuario era de una ventana nueva en blanco que no hacía nada.

Un workaround rápido fue declarar que los buscadores tuviesen solo acceso de lectura a la sesión. Como todos los buscadores derivaban de un formulario padre para búsquedas fue fácil y rápido añadir el atributo EnableSessionState=”ReadOnly” en la directiva @Page de dicho formulario. Y problema solucionado…

…o más bien parche aplicado, porque la solución real pasaría por la inversa: declarar que el acceso habitual a la sesión es de “solo lectura” (añadiendo <pages enableSessionState="ReadOnly" /> en el web.config) y declarar acceso de lectura y escritura solo en aquellos formularios que quieran escribir en la sesión (usando EnableSessionState=”true” en la directiva @Page). De hecho sería incluso mejor deshabilitar en el web.config el acceso a sesión (colocando false en el enableSessionState) y colocar explícitamente los valores ReadOnly y true a cada formulario que requiera acceder a la sesión (en modo de solo lectura o con permisos totales). Pero ese es un refactoring mucho más complejo, claro.

Ten presente pues que si tus peticiones declaran que pueden acceder en modo lectura y escritura a la sesión nunca se ejecutarán de forma concurrente (para un mismo usuario). Quizá no te des por aludido porque tu aplicación no abre varias ventanas pero… ¿haces varias llamadas AJAX de forma simultánea? Si es así… ¿qué permisos tienen al respecto de la sesión?.

Si en lugar de webforms usas ASP.NET MVC recuerda que puedes aplicar el atributo [SessionState] para indicar que un controlador requiere un acceso a la sesión distinto del que esté indicado por defecto en el web.config (o que no requiere acceso en absoluto).

¿Mi recomendación? Evita usar la sesión en todo lo que puedas. Recuerda que en ASP.NET los datos de autenticación no se guardan en la sesión (tienen su propia cookie separada). Pero si al final te decides a usarla, desactívala por defecto en el web.config y actívala explícitamente en todas aquellas páginas/controladores que la requieran. Y cuando la actives actívala siempre en modo ReadOnly a no ser que realmente debas escribir en ella, claro ;)

Con esos sencillos pasos conseguirás evitar que se te queden peticiones enganchadas esperando por una sesión que a lo mejor ni necesitan!

Saludos!

Publicado por Eduard Tomàs i Avellana con no comments
Archivado en: ,

Securiza tu WebApi con tokens autogenerados

El escenario que vamos a abordar en este post es el siguiente: tienes una API creada con ASP.NET WebApi y quieres que sea accesible a través de un token. Pero en este caso quieres ser tu quien proporcione el token y no un tercero como facebook, twitter o Azure Mobile Services (como p. ej. en el escenario que cubrimos en este post). Para ello nuestra API expondrá un endpoint en el cual se le pasarán unas credenciales de usuario (login y password) para obtener a cambio un token. A partir de ese momento todo el resto de llamadas de la API se realizarán usando este token y las credenciales del usuario no seran necesarias más.

Para empezar crea un proyecto ASP.NET con el template “Empty” pero asegúrate de marcar la checkbox de “Web API” para que nos la incluya por defecto. Luego agregamos los paquetes para hospedar OWIN, ya que vamos a usar componentes OWIN tanto para la creación de los tokens oAuth como su posterior validación. Así pues debes incluir los paquetes “Microsoft.AspNet.WebApi.Owin” y “Microsoft.Owin.Host.SystemWeb”.

El siguiente paso será crear una clase de inicialización de Owin (la famosa Startup). Para ello puedes hacer click con el botón derecho sobre el proyecto en el solution explorer y seleccionar “Add –> OWIN Startup class” o bien crear una clase normal y corriente llamada Startup. El código inicial es el siguiente:

  1. [assembly: OwinStartup(typeof(OauthProviderTest.Startup))]
  2.  
  3. namespace OauthProviderTest
  4. {
  5.     public class Startup
  6.     {
  7.         public void Configuration(IAppBuilder app)
  8.         {
  9.             var config = new HttpConfiguration();
  10.             WebApiConfig.Register(config);
  11.             ConfigureOAuth(app);
  12.             app.UseWebApi(config);
  13.         }
  14.     }
  15. }

La clase WebApiConfig es la que configura WebApi y la generó VS al crear el proyecto (está en la carpeta App_Start). Nos falta ver el método ConfigureOAuth que veremos ahora mismo. Observa que el método ConfigureOAuth se llama antes del app.UseWebApi, ya que vamos a añadir middleware OWIN en el pipeline http y debemos hacerlo antes de que se ejecute WebApi. Y por cierto, dado que ahora inicializamos nuestra aplicación usando OWIN puedes eliminar el fichero Global.asax, ya que no lo necesitamos para nada.

Veamos ahora el método ConfigureOAuth. En este método debemos añadir el middleware OWIN para la creación de tokens OAuth. Para ello podemos usar el siguiente código:

  1. public void ConfigureOAuth(IAppBuilder app)
  2. {
  3.     var oAuthServerOptions = new OAuthAuthorizationServerOptions()
  4.     {
  5.         AllowInsecureHttp = true,
  6.         TokenEndpointPath = new PathString("/token"),
  7.         AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
  8.         Provider = new CredentialsAuthorizationServerProvider(),
  9.     };
  10.     app.UseOAuthAuthorizationServer(oAuthServerOptions);
  11. }

Con ello habilitamos un endpoint (/token) para generar los tokens oAuth. El proveedor de dichos tokens es la clase CredentialsAuthorizationServerProvider (que veremos a continuación). Esta clase será la que recibirá las credenciales (login y password), las validará y generará un token oAuth.

Por supuesto nos falta ver el código para validar las credenciales y aquí es donde entra la clase CredentialsAuthorizationServerProvider. Esa clase es la que recibe el login y el password del usuario, los valida y crea el token oAuth:

  1. public class CredentialsAuthorizationServerProvider : OAuthAuthorizationServerProvider
  2. {
  3.     public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
  4.     {
  5.         context.Validated();
  6.     }
  7.  
  8.     public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
  9.     {
  10.  
  11.         context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
  12.  
  13.         using (TestContext db = new TestContext())
  14.         {
  15.             var user = db.Users.FirstOrDefault(u => u.Login == context.UserName && u.Password == context.Password);
  16.             if (user == null)
  17.             {
  18.                 context.SetError("invalid_grant", "The user name or password is incorrect.");
  19.                 return;
  20.             }
  21.         }
  22.  
  23.         var identity = new ClaimsIdentity(context.Options.AuthenticationType);
  24.         identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
  25.         identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
  26.         context.Validated(identity);
  27.     }
  28. }

Lo único remarcable es la primera línea del método GrantResourceOwnerCredentials que se encarga de habilitar CORS. WebApi tiene soporte para CORS, pero el endpoint /token no está gestionado por WebApi si no por el middleware OWIN así que debemos asegurarnos de que mandamos las cabeceras para habilitar CORS. El resto del código es un acceso a la BBDD usando un contexto de EF para encontrar el usuario con el login y el password correcto. Por supuesto en un entorno de producción eso no se haría así. Este código no tiene en cuenta que las passwords deben guardarse como un hash en la BBDD. Si quieres acceder a BBDD directamente debes generar el hash de los passwords aunque si una mejor opción es usar Identity (y la clase UserManager) para acceder a los datos de los usuarios. Una vez validado que las credenciales son correctas creamos la ClaimsIdentity y generamos el token correspondiente.

Para probarlo podéis hacer un POST a /token con las siguientes características:

  • Content-type: application/x-www-form-urlencoded
  • En el cuerpo de la petición añadir los campos:
    • grant_type = “password”
    • username = “Login del usuario”
    • password = “Password del usuario”

Es decir como si tuvieses un formulario con esos campos y lo enviaras via POST al servidor. Os pongo una captura de la petición usando postman:

image

Se puede ver que la respuesta es el token, el tiempo de expiración (que se corresponde con el valor de la propiedad AccessTokenExpireTimeSpan) y el tipo de autenticación que es bearer (ese es el valor que deberemos colocar en la cabecera Authorization).

A partir de ese punto tenemos un token válido y nos olvidamos de las credenciales del usuario. Al menos hasta que el token no caduque. Cuando el token caduque, se deberá generar uno nuevo con otro POST al endpoint /token.

El siguiente punto es habilitar WebApi para que tenga en cuenta esos tokens. Hasta ahora nos hemos limitado a generar tokens, pero WebApi no hace nada con ellos. De hecho no habilitamos WebApi si no que añadimos otro módulo OWIN para autenticarnos en base a esos tokens. El proceso ocurre antes y es transparente a WebApi. Para ello debemos añadir las siguientes líneas a la clase Startup al final del método ConfigureOAuth:

  1. var authOptions = new OAuthBearerAuthenticationOptions()
  2. {
  3.     AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active
  4. };
  5. app.UseOAuthBearerAuthentication(authOptions);

Así añadimos el módulo de OWIN que autentica en base a esos tokens. Para hacer la prueba vamos a crear un controlador de WebApi y vamos a indicar que es solo para usuarios autenticados:

  1. [Authorize]
  2. public class SecureController : ApiController
  3. {
  4.     public IHttpActionResult Get()
  5.     {
  6.         return Ok("Welcome " + User.Identity.Name);
  7.     }
  8. }

Y ahora para probarlo hacemos un GET a la URL del controlador (/api/secure) y tenemos que pasar la cabecera Authorization. El valor de dicha cabecera es “Bearer <token>”:

image

Y con esto deberíamos obtener la respuesta del controlador. En caso de que no pasar la cabecera o que el token fuese incorrecto el resultado sería un HTTP 401 (no autorizado).

Unas palabras sobre los tokens

Fíjate que en ningún momento guardamos en BBDD los tokens de los usuarios y esos tokens son válidos durante todo su tiempo de vida incluso en caso de que el servidor se reinicie. Eso es así porque el token realmente es un ticket de autenticación encriptado. El middleware de OWIN cuando recibe un token se limita a desencriptarlo y en caso de que sea válido, extrae los datos (los claims de la ClaimIdentity creada al generar el token) y coloca dicha ClaimIdentity como identidad de la petición. Es por eso que en el controlador podemos usar User.Identity.Name y recibimos el nombre del usuario que entramos.

Por lo tanto cualquier persona que intercepte el token podrá ejecutar llamadas “en nombre de” el usuario mientras este token sea válido. A todos los efectos poseer el token de autenticación equivale a poseer las credenciales del usuario, al menos mientras el token sea válido. Tenlo presente si optas por ese mecanismo de autenticación: si alguien roba el token, la seguridad se ve comprometida mientras el token sea válido. Por supuesto eso no es distinto al caso de usar una cookie, donde el hecho de robar la cookie compromete la seguridad de igual forma. Y los tokens son más manejables que las cookies y dan menos problemas, en especial en llamadas entre orígenes web distintos.

¡Espero que este post os resulte interesante!

Publicado por Eduard Tomàs i Avellana con 3 comment(s)
Archivado en: ,

Aurelia… lo nuevo después de Durandal

Seguro que muchos de vosotros conocéis Durandal, la librería para crear aplicaciones SPA ideada por Rob Eisenberg autor también de otras librerías como Caliburn y con cierta obsesión por las espadas…

Hacé algún tiempo Rob anunció que se había unido al equipo de Angular para colaborar con el desarrollo de la versión 2.0 de dicha librería y que Angular y Durandal convergerían en una única librería. Eso generó muchas reacciones, algunas positivas, otras no tanto. A pesar de que Durandal tenía sus usuarios la verdad es que no había conseguido, ni mucho menos, la popularidad de Angular que con esas noticias parecía convertirse en el standard de facto para crear aplicaciones SPA (si es que ya no lo era).

Posteriormente Rob anunció que dejaba el equipo de Angular debido a diferencias en como tenía que evolucionar la librería. Así que anunció que Durandal seguiría su camino y que evolucionaría de forma independiente a Angular y que en fin… todo seguía más o menos como antes. También prometía novedades sobre Durandal NextGen, la versión que tenía en mente y que nunca hubiese existido si Rob se hubiese quedado en el equipo de Angular.

Y finalmente Rob anuncia la aparición de Aurelia, su nuevo framework para aplicaciones SPA. Ya… para seguir la tradición lo podría haber llamado Hrunting o Glamdring o ya puestos Tizona pero no… ha optado por Aurelia. Nuevo nombre para dejar claro que no se trata de una evolución de Durandal si no de una librería totalmente nueva, diseñada desde cero y pensada para ser moderna: está escrita integramente en ES6 aunque transpilada (vaya palabreja) y pollyfilizada (otra palabreja… lo siento por los de la RAE) para trabajar con los navegadores evergreen de hoy en día (un navegador evergreen es aquel del cual no importa su versión por estar siempre actualizado. Si te pregunto tu versión de Chrome o Firefox dudo que la sepas exactamente, esos son los navegadores evergreen).

A diferencia de Angular, Aurelia es más un conjunto de librerías que de forma conjunta forman un framework. Así se pueden usar las librerías sueltas para pequeñas tareas o bien utilizarlas todas en conjunto para crear aplicaciones SPAs. Eso tiene la ventaja de que uno solo paga por lo que usa y que la curva de aprendizaje es, a priori, más suave en tanto se pueden incorporar los conceptos de forma más independiente (en Angular todo está muy atado y es difícil explicar los conceptos de forma separada).

Empezando con Aurelia

Las instrucciones para empezar están todas en http://aurelia.io/get-started.html. Básicamente antes se tiene que instalar gulp y jspm. El primero se usará como sistema de build y el segundo se usa como gestor de paquetes. Esas dos herramientas no forman parte de Aurelia como tal y podrías usar grunt o bower si lo prefieres (asumiendo que los paquetes de Aurelia estén subidos en bower que no lo sé), pero todos los ejemplos de la página web de Aurelia están en glup y jspm.

Veamos lo mínimo necesario para usar Aurelia. Para ello partimos de un directorio vacío y ejecutamos jspm install aurelia-bootstrapper lo que nos instalará un montón de dependencias.

Ahora vamos a necesitar crear tres ficheros:

  • El bootstrapper o lanzador de nuestra aplicación
  • La vista
  • El viewmodel

Vemos pues que Aurelia usa el patrón MVVM y en efecto tiene enlace bidireccional entre la vista y el viewmodel asociado. Veamos cada uno de esos componentes.

El lanzador (index.html) es el encargado de cargar aurelia y lanzar la vista inicial:

  1. <!doctype html>
  2. <html>
  3. <head>
  4. </head>
  5. <body aurelia-app>
  6.     <script src="jspm_packages/system.js"></script>
  7.     <script src="config.js"></script>
  8.     <script>
  9.     System.baseUrl = 'myapp';
  10.     System.import('aurelia-bootstrapper').catch(console.error.bind(console));
  11.     </script>
  12. </body>
  13. </html>

La línea clave aquí es la que establece System.baseUrl a “myapp”. Eso le indica a Aurelia que cargue la vista myapp/app.html y el viewmodel asociado myapp/app.js.

Empecemos por ver el viewmodel que es una clase pura de JavaScript:

  1. export class Welcome{
  2.     constructor(){
  3.         this.name = 'Eduard Toms';
  4.         this.twitter = '@eiximenis';
  5.     }
  6.  
  7.     get twitterUri() {
  8.         return `http://twitter.com/${this.twitter}`;
  9.     }
  10.  
  11.     welcome(){
  12.         alert(`Welcome ${this.name}!`);
  13.     }
  14. }

Aunque quizá no te lo parezca eso es JavaScript puro, concretamente EcmaScript 6, que tiene entre otras características la posibilidad de definir módulos y clases. En este caso el el fichero myapp/app.js es un módulo que se limita a exportar una sola clase llamada Welcome.

Observa también que las propiedades twitterUri() y welcome() usan cadenas pero con el carácter ` (tilde abierta) y no con la comilla simple (ya, el coloreado de código falla totalmente :p). Esa es otra característica de ES6 llamada string interoplation. Sí… ES6 viene cargado con un montón de novedades.

Finalmente nos queda la vista. Las vistas son pedazos de html encapsulados dentro de una etiqueta template:

  1. <template>
  2.     <section>
  3.         <h2>${heading}</h2>
  4.         <form role="form" submit.delegate="welcome()">
  5.             <div class="form-group">
  6.                 <label for="fn">First Name</label>
  7.                 <input type="text" value.bind="name" class="form-control" id="fn" />
  8.             </div>
  9.             <div class="form-group">
  10.                 <label for="ln">Twitter</label>
  11.                 <input type="text" value.bind="twitter" class="form-control" id="ln" />
  12.             </div>
  13.             <div class="form-group">
  14.                 <label>Hello ${name}. Check your twitter account (<a href.bind="twitterUri">${twitter}</a>)?</label>
  15.                 <p class="help-block">${fullName}</p>
  16.             </div>
  17.             <button type="submit" class="btn btn-default">Submit</button>
  18.         </form>
  19.     </section>
  20. </template>

Puedes observar como los enlaces en texto se definen con la sintaxis ${propiedad} (estos enlaces son obviamente unidireccionales). Por otro lado cuando enlazas una propiedad (tal como value o href) se usa la sintaxis propiedad.bind=”propiedad” (p. ej. value.bind o href.bind).

Si ejecutas index.html eso en un navegador que soporte ES6 (p. ej. Chrome) deberás ver algo parecido a:

image

(El alert aparece al pulsar el botón de submit).

Por supuesto si el navegador que tienes no soporta ES6 siempre puedes usar 6to5 para transpilar (es decir convertir xD) el código ES6 usado al ES5 soportado hoy en día para la mayoría de navegadores. Aquí es donde entra gulp en el proceso.  Otra opción es usar directamente ES5 si prefieres. Pero bueno… veremos esto en otro post :)

Y bueno… tampoco me ha dado tiempo para ver mucho más de Aurelia. Por lo poco que he visto la idea en general es parecida a Angular o Ember en tanto que usa el patrón MVVM (el viewmodel de Aurelia se corresponde aproximadamente al controlador de Angular) y que soporta enlaces bidireccionales.

Os animo por supuesto a que le echéis un vistazo, al menos para conocerla brevemente… veremos si gana popularidad y se convierte en una alternativa a Angular o se queda en el intento. :)

Saludos!

Publicado por Eduard Tomàs i Avellana con no comments
Archivado en: ,

Evento–Bilbostack 2015

Buenas! El sábado (17 de Enero) tuve el placer de asistir al Bilbostack, una conferencia para desarrolladores que se celebra anualmente en Bilbao. En primer lugar felicitar a los organizadores porque la conferencia estaba muy bien montada: el sitio (la universidad de Deusto) era genial al igual que las dos salas usadas para los dos tracks paralelos que había. La primera, el auditorio, simplemente espectacular. La segunda, la sala Gárate, se quedó pequeña en alguna charla (la de IoT con gadgeeter que impartió Quique) pero bueno…

Yo tuve el placer de dar una charla hablando sobre Reactjs, ya sabéis “la librería esa de Facebook” que está ganando mucha tracción ultimamente y que propone un modelo diferente para gestionar nuestra UI. Tengo varios posts a medias sobre Reactjs que espero poder irlos terminando y sacando en breve ;-)

Sobre las charlas en general todas muy buenas… Una mención especial para Hugo Biarge que eligió un tema que puede ser muy pero que muy farragoso (las directivas de Angular) pero que contó magistralmente… vamos que creo que incluso alguno se enteró hasta de lo que era la transclusión :P

Y otra mención especial para Javi Santana: su charla sobre la arquitectura de CartoDB estuvo muy pero que muy bien. No entró en mucho detalle, tuvo un poco de “he venido a vender el producto”, pero lo mejor de la charla fueron “los troleos” que iba soltando, todos ellos muy interesantes y acertados (troleos contra los que quieren usar solo lo último, migrar por si, o usar el nuevo requeteframework sin valorar las implicaciones). Lo dicho, una gran charla para terminar el Bilbostack.

Luego del evento hubo comida-networking-y-todo-eso por la Plaza Nueva tomando los pintxos “en el bar de Jon”… brutales :)

Os dejo con el enlace a slideshare de mi presentación.

Ah sí… Y se me olvidaba, jejejeje… felicitar de nuevo a los organizadores por haber hecho que el Beerbao fest coincidiese con el Bilbostack :P. ¿Qué mejor manera de terminar un evento se os ocurre que tomarse unas buenas cervezas artesanas, eh? ;-)

Saludos!!!!

PD: Para ver más info y fotos del Bilbostack os recomiendo seguir a su cuenta de twitter y buscar por el hastag #Bilbostack.

Publicado por Eduard Tomàs i Avellana con no comments
Archivado en: ,

update-database y LocalDb en una aplicación de escritorio

Estos días he estado desarrollando un aplicación de escritorio (wpf aunque eso es lo de menos) que va a hacer uso de LocalDb para guardar datos. Ciertamente no es un escenario muy habitual, ya que al instalar la aplicación en un ordenador cliente se requiere instalar LocalDb pero en este caso eso era asumible. Otras opciones para escritorio podrían pasar por usar algúna BBDD de proceso (como VistaDb o similares).

“Teoricamente” eso no debería diferir del workflow usado en aplicaciones web. Supongamos que tenemos nuestro proyecto con el contexto de EF creado y hemos habilitado migrations (enable-migrations) para controlar las modificaciones del esquema.

Supongamos una cadena de conexión que use AttachDbFilename:

  1. <add name="DbClient" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\App_Data\clientdb_v1.mdf;Initial Catalog=clientdb-v1;Integrated Security=True" providerName="System.Data.SqlClient" />

Se puede observar que se usa |DataDirectory| al igual que en una aplicación web. Esta es una variable de “entorno” que entiende el .NET Framework (a partir de la 4.0.2) y cuyos valores están definidos de la siguiente manera, según se cuenta en este post.

By default, the |DataDirectory| variable will be expanded as follow:

- For applications placed in a directory on the user machine, this will be the app's (.exe) folder.
- For apps running under ClickOnce, this will be a special data folder created by ClickOnce
- For Web apps, this will be the App_Data folder

Under the hood, the value for |DataDirectory| simply comes from a property on the app domain. It is possible to change that value and override the default behavior by doing this:

      AppDomain.CurrentDomain.SetData("DataDirectory", newpath)

Por lo tanto tenemos que para aplicaciones de escritorio |DataDirectory| se mapea al directorio donde está la BBDD según este post. He de decir que en mi experiencia eso NO es cierto. Se mapea a la subcarpeta App_Data de la carpeta donde está el ejecutable (así, si tenemos el ejecutable en c:\xxx\bin\Debug se mapeará a c:\xxx\bin\Debug\App_Data). Quizá es un cambio posterior a la publicación de este post.

En ejecución se espera que la BBDD (el fichero .mdf) esté en este directorio. Perfecto, pero ahora tenemos el problema de las herramientas de VS. Antes de nada, agregamos un archivo .mdf a nuestra solución (en el startup project):

image

Con esto ya podemos ejecutar update-database y transferir todas las migraciones a este fichero .mdf.

Pero ahora tenemos un problema, y es que los ficheros .mdf son tratados como un fichero “content” tradicional, es decir que lo máximo que VS puede hacer es copiarlos al output folder:

image

Si marcamos:

  1. Copy always: Cada vez que compilemos el proyecto se copiará el .mdf hacia el directorio de ejecución. Resultado: perderemos todos los datos, dado que el .mdf de la solución está vacío (solo tiene el esquema generado por update-database).
  2. Copy if newer: Solo se copiará en caso que el .mdf de la solución sea más nuevo, lo que solo ocurrirá en el caso de cambios de esquema. Entonces en cada cambio de esquema perdemos los datos.
  3. Do not copy: El fichero .mdf no se copia en el directorio de salida, lo que implica que la aplicación… no lo encontrará.

Ninguna de las 3 opciones es deseable. Esto en aplicaciones web no ocurre, debido a la forma en como DataDirectory es gestionado por ASP.NET, pero ahora estamos en escritorio :(

Una posible solución es olvidarnos de la copia (Do not copy) y hacer que DataDirectory apunte al fichero .mdf de la solución. Para ello se puede usar AppDomain.CurrentDomain.SetData para conseguir el efecto deseado:

  1. var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\..\..\MyClient\App_Data";
  2. AppDomain.CurrentDomain.SetData("DataDirectory", path);

Básicamente obtenemos el directorio de ejecución del assembly y nos movemos hacia el directorio donde hay realmente el fichero en la solución.

Eso hace que ahora tanto VS al usar update-database como nuestra aplicación usen el mismo fichero .mdf. Por supuesto en cuanto despleguemos de verdad la app deberemos utilizar otra técnica. Porque así dependemos de un fichero (el .mdf) que NO está desplegado en el directorio de salida.

Una solución para ello es volver a poner el “Copy aways” (por lo que el .mdf se copiará cada vez, lo que ahora  no es problema porque tiene datos y esquema) y ejecutar o no la llamada a AppDomain.CurrentDomain.SetData según sea de menester (si se ejecuta la llamada se usa el .mdf de la solución, en caso contrario se usa el .mdf localizado en el directorio de salida).

Un saludo!

Publicado por Eduard Tomàs i Avellana con no comments
Archivado en: ,

Securizando tus servicios WebApi usando OWIN

Hace algún tiempo escribí un post acerca de mecanismos para hacer tus servicios WebApi seguros. En este post mencionaba tres mecanismos para que tus servicios web solo sean accesibles a través de usuarios autenticados:

  • Atributo Authorize custom: no recomendado para autenticación (es para autorización).
  • Message Handler (DelegatingHandler): Mecanismo recomendado dentro de WebApi para autenticación.
  • HttpModule: Solución a nivel de IIS.

En el post comentaba que si tu aplicación depende de IIS puedes usar un HttpModule, pero que si quieres evitar tener una dependencia con él (ya que WebApi admite escenarios selfhost) debes usar un DelegatingHandler.

Pero este post está escrito antes de que OWIN fuese una realidad y con la aparición de OWIN existe otro mecanismo para autenticar nuestros servicios WebApi: usar un middleware OWIN.

Usar un middleware OWIN une las dos ventajas de los Message Handlers y los HttpModules:

  1. Al igual que un HttpModule funciona antes que WebApi por lo que autentica no solo llamadas a WebApi si no también llamadas a otros componentes (p. ej. MVC) que podamos tener.
  2. Al igual que un DelegatingHandler no está ligado a IIS en tanto que OWIN no está ligado a ningún servidor web en concreto.

El objetivo de este post es ver como crear un middleware OWIN para autenticar nuestros servicios WebApi, aunque si usáramos algún otro componente compatible con OWN (p. ej. NancyFx) nos serviría para autenticar las llamadas a ese otro componente también.

Vamos a empezar creando una aplicación ASP.NET Empty con WebApi agregado:

image

Luego agregaremos los paquetes Katana (para los despistados: Katana es una implementación de paquetes OWIN desarrollada por MS). En concreto instalaremos con NuGet el paquete Microsoft.Owin.Host.SystemWeb y este instalará ya todas las dependencias.

Creando el middleware OWIN

Un middleware OWIN es algo extraordinariamente simple. Es tan simple que no es ni una clase. Es tan solo un delegado. En su forma más básica un middleware OWIN es un Func<IDictionary<string, object>, Task>.

Aunque podríamos registrar directamente un delegado con esa firma, el sistema nos permite también registrar una clase, siempre y cuando tenga la siguiente estructura:

  1. using AppFunc = Func<IDictionary<string, object>, Task>;
  2. public class BasicAuthMiddleware
  3. {
  4.     private readonly AppFunc _next;
  5.     public BasicAuthMiddleware(AppFunc next)
  6.     {
  7.  
  8.     }
  9.  
  10.     public async Task Invoke(IDictionary<string, object> environment)
  11.     {
  12.  
  13.     }
  14. }

Este es el esqueleto básico para crear un middleware OWIN. Ahora hagamos que este middleware haga algo. Lo que hará será validar que existe autenticación básica (es decir que la cabecera HTTP Authorization llega y tiene el valor “Basic XXXX” donde XXXX es la cadena Base64 resultado de concatenar un login y un password separados por dos puntos (:).

Para ser súper genérico y no atarse a nadie, OWIN no define clases para acceder a las cabeceras HTTP, al cuerpo HTTP ni a nada. En su lugar tan solo se define el entorno: un diccionario de claves (cadena) valores (objeto). Pocas cosas hay más genéricas que eso.

¿Y qué valores tienen esas claves? Pues hay algunas que define la propia especificación, pero luego tu módulo OWIN podría añadir las que quisiera… no dejan de ser un mecanismo de comunicación entre middlewares.

Veamos ahora el código del método

El código del constructor de la clase es trivial (simplemente nos guardamos en _next el valor del parámetro que recibimos).

Veamos el código del método Invoke:

  1. public async Task Invoke(IDictionary<string, object> environment)
  2. {
  3.     var auth = GetAuthHeader(environment);
  4.     if (!string.IsNullOrEmpty(auth))
  5.     {
  6.         if (auth.StartsWith("Basic "))
  7.         {
  8.             auth = auth.Substring("Basic ".Length);
  9.             var values = DecodeAuthValue(auth);
  10.             // Validaramos login (values.Item1) y pwd (values.Item2) aqu.
  11.             // En este ejemplo suponemos que cualquier combinacin es OK
  12.             PutAuthenticationInfo(environment, values.Item1);
  13.         }
  14.     }
  15.     await _next.Invoke(environment);
  16. }

Simplemente recogemos el valor del header Authorization y si existe y tiene el formato esperado (Basic <CadenaBase64>) lo decodificamos y colocamos la identidad del usuario en el contexto de OWIN.

El método GetAuthHeader obtiene el valor de la cabecera Authorization:

  1. private string GetAuthHeader(IDictionary<string, object> environment)
  2. {
  3.     var headers = environment["owin.RequestHeaders"] as IDictionary<string, string[]>;
  4.     if (headers != null && headers.ContainsKey("Authorization"))
  5.     {
  6.         return headers["Authorization"].FirstOrDefault();
  7.     }
  8.     return null;
  9. }

Los headers se almacenan dentro del contexto en la clave owin.RequestHeaders y el valor de dicha clave es un diccionario con todos los headers. Dado que un header puede tener varios valores el diccionario es de cadena (nombre del header) a valor (array de cadenas). En este caso devolvemos tan solo el primer que haya.

El método DecodeAuthValue simplemente decodifica la cadena Base64 y devuelve una tupla con el login y el password que estaban en la cabecera:

  1. private Tuple<string, string> DecodeAuthValue(string auth)
  2. {
  3.     var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(auth));
  4.     var tokens = decoded.Split(':');
  5.     return new Tuple<string, string>(tokens[0], tokens.Length > 1 ? tokens[1] : null);
  6. }

Y finalmente el método PutAuthenticationInfo es el que crea el IPrincipal y lo coloca dentro del contexto de OWIN. Ya no debemos colocarlo en HttpContext.Current ni Thread.CurrentPrincipal. Debemos colocarlo dentro de la clave “server.User” del entorno OWIN:

  1. private void PutAuthenticationInfo(IDictionary<string, object> environment, string user)
  2. {
  3.     var claim = new Claim(ClaimTypes.Name, user);
  4.     var identity = new ClaimsIdentity(new[] {claim}, "Basic");
  5.     environment["server.User"] = new ClaimsPrincipal(identity);
  6. }

En este caso simplemente creamos un ClaimsPrincipal (con el nombre del usuario) y lo colocamos dentro del contexto.

El último punto es  configurar OWIN para que use nuestro middleware. Añade una clase, llámala Startup y coloca el siguiente código:

  1. [assembly: OwinStartup(typeof(OwinSecureDemo.Startup))]
  2.  
  3. namespace OwinSecureDemo
  4. {
  5.     public class Startup
  6.     {
  7.         public void Configuration(IAppBuilder app)
  8.         {
  9.             app.Use(typeof (BasicAuthMiddleware));
  10.         }
  11.     }
  12. }

Finalmente para probar creamos un controlador WebApi y lo decoramos con [Authorize]:

  1. [Authorize]
  2. public class BeersController : ApiController
  3. {
  4.     public IEnumerable<string> Get()
  5.     {
  6.         return new[] {"Estrella", "Voll Damm"};
  7.     }
  8. }

¡Y listos! Ya puedes probarlo pasando un valor válido a la cabecera Authorization y ver que todo funciona:

curl "http://localhost:23850/api/Beers" -H "Accept: application/json" -H "Authorization: Basic ZWl4aW1lbmlzOnB3ZA=="

Luego quitas la cabecera Authorization de la llamada y deberías recibir un error de usuario no autenticado.

Hemos visto lo sencillo que es usar un middleware OWIN para autenticación y además lo hemos implementado desde cero (sin usar ninguna clase dependiente de Katana tal como OwinContext u OwinRequest). Katana ofrece clases wrappers sobre el contexto OWIN para no tener que andar metiendo claves “a mano” en el diccionario que es el contexto de OWIN, pero eso ya sería motivo de otro post.

Un saludo!

Publicado por Eduard Tomàs i Avellana con no comments
Archivado en: ,,

Securizar tu WebApi con Azure Mobile Services

El escenario es el siguiente: tenemos un conjunto de servicios WebApi que no tienen porque estar desplegados en Azure pero queremos que esos servicios solo sean accesibles para usuarios que se hayan autenticado previamente a través de un proveedor externo (p. ej. Twitter) usando la infrastructura de Azure Mobile Services.

Creación y configuración de Mobile Services

Esa es la parte fácil… Una vez hemos creado nuestro mobile service debemos irnos a la pestaña Identity y colocar los valores que nos pide según el proveedor de autenticación externo que queramos usar. En este ejemplo usaremos twitter así que nos pide el Api Key y el Api Secret:

image

Estos valores nos lo da twitter cuando creamos una aplicación en apps.twitter.com:

image

Otro aspecto a tener en cuenta es que twitter nos pide la URL de callback, esa debe ser la URL de nuestro mobile service añadiendo el sufijo /signin-twitter.

Pero bueno… todos esos detalles están explicados fenomenalmente en la propia ayuda de Azure.

Autenticarse usando twitter a través de Mobile Services (WAMS)

El cliente de Azure Mobile Services realiza un trabajo genial a la hora de gestionar todo el flujo oAuth para autenticar a usuarios usando cualquiera de los proveedores que soporta (Facebook, twitter, Google, una cuenta de Live ID). Si creas una aplicación Windows 8.1 o bien Windows Phone, autenticarse usando un proveedor externo (twitter) a través de Mobile Services es trivial:

  1. public async void PerformAuth()
  2. {
  3.     MobileServiceClient client = new MobileServiceClient("https://beerlover.azure-mobile.net/");
  4.     var user = await client.LoginAsync(MobileServiceAuthenticationProvider.Twitter);
  5.     JwtToken = user.MobileServiceAuthenticationToken;
  6. }

Este código se encarga de gestionar todo el flujo OAuth y mostrar la UI correspondiente para que el usuario pueda introducir su login y password de twitter. Al final guarda en una propiedad JwtToken el token de autenticación retornado por WAMS.

En este punto finaliza nuestra interacción con mobile services: lo hemos usado para que el usuario se pudiese autenticar de forma fácil con un proveedor externo. Y al final obtenemos un token. Ese token no es de twitter, ese token es de WAMS y es un token JWT.

Json Web Token – JWT

JWT es un formato que define datos que puede incorporar un token que realmente es un objeto JSON. Es un formato que se está usando bastante ahora y tienes su especificación aquí.

Si ejecuto una aplicación que tenga el código anterior y me autentico usando twitter puedo ver como es el token JWT que me devuelve Mobile Services:

image

Bueno… es una ristra de carácteres bastante larga, pero básicamente se trata del objeto JSON codificado en Base64.

Para ver el contenido, puedes copiar el token y usar jwt.io para descodificar el token:

image

Podemos ver que hay tres colores en la imagen anterior y cada uno se corresponde a una parte. El primer color (verde) es un JSON con el siguiente formato:

  1. {
  2.   "typ": "JWT",
  3.   "alg": "HS256"
  4. }

Esto se conoce como JWT Envelope y nos indica el formato del token que viene a continuación (JWT) y el algoritmo usado para la firma digital (HS256).

La siguiente parte del token (azul) es realmente el JSON con los datos:

  1. {
  2.   "iss": "urn:microsoft:windows-azure:zumo",
  3.   "aud": "urn:microsoft:windows-azure:zumo",
  4.   "nbf": 1418892674,
  5.   "exp": 1421484674,
  6.   "urn:microsoft:credentials": "{\"accessToken\":\"84274067-6C7zM6rnbL5VAIF8ARIZXWg6XTZ49x67klxRTAyIU\",\"accessTokenSecret\":\"fGDwXuJg7i8rJqwJFTki5EVbEiLvgx3MlCvunOaNOm81X\"}",
  7.   "uid": "Twitter:84274067",
  8.   "ver": "2"
  9. }

Realmente cada uno de esos campos es un claim (JWT está basado en claims).

De esos claims nos interesan realmente dos. El claim iss que contiene el issuer o quien ha generado el token y el claim aud que contiene la audience que indica para quien va dirigido el token. En este caso iss nos indica que el token ha sido generado por Mobile Services y el token aud nos indica que el token es para consumo de Mobile Services. Podemos ver más claims como uid donde hay un ID de usuario.

La última parte (en rojo en la imágen) es la firma digital del token. En este caso el Envelope nos indica que la firma digital es HS256 (HMAC usando SHA256) y eso nos sirve para comprobar que el token es válido.

Securizando nuestros servicios WebApi

La idea para securizar nuestros servicios WebApi es muy simple: A cada petición de nuestros servicios miraremos si se nos pasa el token JWT en la cabcera HTTP Authentication. Si recibimos un token JWT lo parsearemos y comprobaremos la firma digital (podríamos comprobar además todos los claims que queramos). Si la firma digital es válida, el token es válido y la petición se considera que proviene de un usuario de confianza.

Para validar el token JWT vamos a usar un DelegatingHandler. Ya expliqué en un post anterior sobre como securizar servicios WebApi lo que era un DelegatingHandler.

Para la validación del token JWT nos vamos a ayudar del paquete System.IdentityModel.Tokens.Jwt así que lo primero es agregarlo a la solución. Eso sí agrega la versión 3.0.0.0 mediante el comando:

Install-Package System.IdentityModel.Tokens.Jwt -Version 3.0.0.0

Esa versión es la misma que usa internamente Mobile Services para generar el token JWT.

Así lo primero es crearte una clase (yo la he llamado JwtDelegatingHandler) que derive de DelegatingHandler y redefinir el método SendAsync:

  1. protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  2. {
  3.     if (request.Method != HttpMethod.Options)
  4.     {
  5.         var tokenstr = RetrieveToken(request);
  6.         if (tokenstr != null)
  7.         {
  8.             var handler = new JwtSecurityTokenHandler();
  9.             if (handler.CanReadToken(tokenstr))
  10.             {
  11.                 var token = handler.ReadToken(tokenstr);
  12.                 var secret = GetSigningKey(MasterKey);
  13.                 var validationParams = new TokenValidationParameters()
  14.                 {
  15.                     SigningToken = new BinarySecretSecurityToken(secret),
  16.                     AllowedAudience = "urn:microsoft:windows-azure:zumo",
  17.                     ValidIssuer = "urn:microsoft:windows-azure:zumo"
  18.                 };
  19.                 var principal = handler.ValidateToken(tokenstr, validationParams);
  20.                 Thread.CurrentPrincipal = principal;
  21.                 if (HttpContext.Current != null)
  22.                 {
  23.                     HttpContext.Current.User = principal;
  24.                 }
  25.             }
  26.         }
  27.     }
  28.     return base.SendAsync(request, cancellationToken);
  29. }

El método usa la clase JwtSecurityTokenHandler para mirar si el token JWT es válido sintácticamente y luego llama al método ValidateToken que devuelve un ClaimsPrincipal si el token es correcto. Dicho método valida la firma digital y también comprueba que el issuer y el audence del token sean correctos. Es por ello que los establecemos a los valores que coloca WAMS.

Finalmente si no salta ninguna excepción es que el token es correcto por lo que establecemos el ClaimsPrincipal devuelto como Principal del Thread actual de forma que el atributo [Authorize] entenderá que la petición está autenticada.

El método RetrieveToken obtiene el token JWT de la cabecera:

  1. private static string RetrieveToken(HttpRequestMessage request)
  2.  {
  3.      string token = null;
  4.      IEnumerable<string> authzHeadersEnum;
  5.      bool hasHeader = request.Headers.TryGetValues("Authorization", out authzHeadersEnum);
  6.      if (!hasHeader)
  7.      {
  8.          return null;
  9.      }
  10.  
  11.      var authzHeaders = authzHeadersEnum.ToList();
  12.      if (authzHeaders.Count > 1)
  13.      {
  14.          return null;
  15.      }
  16.  
  17.      var bearerToken = authzHeaders[0];
  18.      token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
  19.      return token;
  20.  }

Y nos queda el método más importante el método GetSigninKey. Dicho método obtiene la clave que usó WAMS para firmar el mensaje y es la misma que debemos usar para comprobar la firma:

  1. internal static byte[] GetSigningKey(string secretKey)
  2. {
  3.     var bytes = new UTF8Encoding(true, true).GetBytes(secretKey);
  4.     using (SHA256Managed managed = new SHA256Managed())
  5.     {
  6.         return managed.ComputeHash(bytes);
  7.     }
  8. }

El parámetro secretKey es la “Master Key” de WAMS que puedes ver en el portal de Azure en la opción de “Manage Keys”:

image

Nota: Si buscas por Internet verás que en muchos sitios agregan la cadena JWTSig a la Master Key. No lo hagas. No sé si en versiones anteriores de WAMS era necesario, pero ahora no lo es.

Con esto ya puedes usar [Authorize] para proteger tus servicios WebApi y que solo sean válidos para aquellas peticiones que tengan un token JWT que proviene de Windows Azure Mobile Services.

Usando System.IdentityModel.Tokens.Jwt 4.0.1

Hemos visto el código necesario para validar el token JWT de WAMS usando la versión 3.0.0.0 de System.IdentityModel.Tokens.Jwt que es la que usa internamente WAMS. Pero la última versión de este paquete (y la que os instalará NuGet si no indicáis versión) es, a la hora de escribir este post, la 4.0.1.

El código no es compatible ya que hay cambios en la clase JwtSecurityTokenHandler. Si usáis la versión 4.0.1 debéis usar el siguiente código en el DelegatingHandler:

  1. var validationParams = new TokenValidationParameters()
  2. {
  3.     IssuerSigningToken = new BinarySecretSecurityToken(secret),
  4.     ValidAudience = "urn:microsoft:windows-azure:zumo",
  5.     ValidIssuer = "urn:microsoft:windows-azure:zumo"
  6. };
  7. SecurityToken outToken;
  8. var principal = handler.ValidateToken(tokenstr, validationParams, out outToken);

Este código es equivalente al anterior, podéis ver que los cambios básicos son nombres de propiedades y la firma de ValidateToken.

Usando Middleware OWIN

Comprobar que System.IdentityModel.Tokens.Jwt en su versión 4.0.1 era compatible con las firmas generadas por WAMS para los tokens JWT supone que podemos usar el middleware OWIN para validar el token JWT y olvidarnos del DelegatingHandler. Si la versión 4.0.1 no hubiese sido compatible (y algo de eso he leído en Internet, al menos en la versión 4.0.0) eso no sería posible ya que el middleware de OWIN depende de dicha versión (realmente la 4.0.0) para la validación de los tokens JWT.

Vale, para agregar el middleware OWIN basta con instalar el paquete Microsoft.Owin.Security.Jwt:

install-package Microsoft.Owin.Security.Jwt

Eso nos instalará las dependencias OWIN que tenemos, pero si no teníamos nada de OWIN instalada deberemos agregar el hosting de OWIN manualmente. Si después de este install-package no tenemos el paquete Microsoft.Owin.Host.SystemWeb deberemos instalarlo. Este paquete es el responsable de integrar el pipeline de OWIN y que se ejecute la clase de Owin Startup. El siguiente paso será crear una clase Startup:

  1. [assembly: OwinStartup(typeof(Beerlover.Server.Startup))]
  2.  
  3. namespace Beerlover.Server
  4. {
  5.     public class Startup
  6.     {
  7.         public void Configuration(IAppBuilder app)
  8.         {
  9.  
  10.             var issuer = "urn:microsoft:windows-azure:zumo";
  11.             var audience = "urn:microsoft:windows-azure:zumo";
  12.             var secret = WebConfigurationManager.AppSettings["ClientSecret"];
  13.  
  14.             var signkey = GetSigningKey(secret);
  15.             app.UseJwtBearerAuthentication(
  16.                 new JwtBearerAuthenticationOptions
  17.                 {
  18.                     AuthenticationMode = AuthenticationMode.Active,
  19.                     AllowedAudiences = new[] { audience },
  20.                     IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
  21.                     {
  22.                         new SymmetricKeyIssuerSecurityTokenProvider(issuer, signkey)
  23.                     },
  24.  
  25.                 });
  26.         }
  27.  
  28.         private byte[] GetSigningKey(string secret)
  29.         {
  30.             var bytes = new UTF8Encoding(true, true).GetBytes(secret);
  31.             using (SHA256Managed managed = new SHA256Managed())
  32.             {
  33.                 return managed.ComputeHash(bytes);
  34.             }
  35.         }
  36.     }
  37. }

Dicha clase configura el middleware de OWIN para validar tokens JWT (a través del método UseJwtBearerAuthentication). De esta manera delegamos en OWIN la validación de los tokens JWT y ya no es necesario hacer nada en WebApi,  es decir ya no debemos usar el DelegatingHandler. Por supuesto debemos seguir usando [Authorize] para indicar que servicios deben ser llamados de forma segura (con token JWT).

La solución usando OWIN es la recomendada ya que la validación de los tokens JWT se hace antes de que la petición llegue siquiera a WebApi y por lo tanto te permite integrarlo en otros middlewares (p. ej. si usas Nancy con OWIN este mismo código te sirve, mientras que el DelegatingHandler es algo propio de WebApi).

Enviando el token desde el cliente

Y simplemente por completitud del post, el código para enviar el token JWT desde el cliente sería el siguiente:

  1. protected void AddJwtToken(HttpClient client)
  2. {
  3.     client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", TwitterAuthService.Instance.JwtToken);
  4. }

Donde TwitterAuthService.Instance.JwtToken contiene el token devuelto por WAMS.

La posibilidad de delegar en WAMS todo el flujo oauth es muy cómoda y ello no nos impide que nuestra propia WebApi que puede estar en Azure (en un Website) o on-premise esté protegida a través del token JWT que emite WAMS. De esa manera la aplicación cliente tiene un solo token que usa tanto para acceder a los servicios WAMS (si los usase) como al resto de la API WebAPi que ni tiene que estar en Azure.

Espero que este post os sea de utilidad.

Publicado por Eduard Tomàs i Avellana con 1 comment(s)
Archivado en: ,,

ASP.NET WebApi: Subida de ficheros

Buenas! Vamos a ver en este post como podemos tratar la subida de ficheros en WebApi.

En ASP.NET MVC la subida de ficheros la gestiona un model binder para el tipo HttpFilePostedBase, por lo que basta con declarar un parámetro de este tipo de datos en el controlador y automáticamente recibimos el fichero subido.

En WebApi el enfoque es muy distinto: en el controlador no recibimos ningún parámetro con el contenido del fichero. En su lugar usamos la clase MultipartFormDataStreamProvider para leer el fichero subido y guardarlo en disco (ambas cosas a la vez).

Anatomía de una petición http con fichero subido

Antes de nada veamos como es una petición HTTP en la que se suba un fichero. Para ello he creado un HTML como el siguiente:

  1. <form method="post" enctype="multipart/form-data">
  2.     File: <input type="file" name="aFile"><br />
  3.     File: <input type="file" name="aFile"><br />
  4.     <input type="submit" value="Submit">
  5. </form>

Selecciono dos ficheros cualesquiera y capturo la petición generada con fiddler. El resultado (eliminando todo lo que no nos importa) es el siguiente:

  1. Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQoYjfxGXTHG6DESL
  2.  
  3. ------WebKitFormBoundaryQoYjfxGXTHG6DESL
  4. Content-Disposition: form-data; name="aFile"; filename="jsio.png"
  5. Content-Type: image/png
  6.  
  7. Contenido binario del fichero
  8. ------WebKitFormBoundaryQoYjfxGXTHG6DESL
  9. Content-Disposition: form-data; name="aFile"; filename="logo_mvp.png"
  10. Content-Type: image/png
  11.  
  12. Contenido binario del fichero
  13. ------WebKitFormBoundaryQoYjfxGXTHG6DESL--

Básicamente:

  • El Content-Type debe ser multipart/form-data
  • El Content-Type debe especificar una boundary. La boundary es un cadena que se usa para separar cada valor de la petición (tanto los ficheros como los valores enviados por formdata si los hubiese).
  • Para cada valor:
    • Se coloca el boundary precedido de --
    • Si es un fichero.
      • se coloca un content-disposition que indica (entre otras cosas) el nombre del fichero
      • El conteido binario del fichero
    • Si no es un fichero (p. ej. es el un formdata que viene de un <input type=text>
      • se coloca un content-disposition que indica el nombre del parámetro
      • Se coloca su valor
  • Finalmente se coloca la boundary para finalizar la petición

Enviar peticiones usando HttpClient

Conociendo como es una petición de subida de ficheros, crearla usando HttpClient es muy simple. El siguiente código sube un fichero:

  1. var requestContent = new MultipartFormDataContent();
  2. var imageContent = new StreamContent(stream);
  3. imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png");
  4. requestContent.Add(imageContent, "image", string.Format("{0:00}.png", idx));

La variable stream es un Stream para acceder al fichero, mientras que la variable idx es un entero que en este caso se usa para dar nombre al fichero subdido (01.png, 02.png, …).

Si capturamos con fiddler como es la petición generada por este código vemos que es como sigue:

  1. POST http://localtest.me:2706/Upload/Photo/568b8c05-aab8-46db-8cbc-aec2a96dec18/2 HTTP/1.1
  2. Content-Type: multipart/form-data; boundary="c609aabb-3872-4d04-a69d-72024c9325a5"
  3. --c609aabb-3872-4d04-a69d-72024c9325a5
  4. Content-Type: image/png
  5. Content-Disposition: form-data; name=image; filename=02.png; filename*=utf-8''02.png

Podemos observar como se ha generado un boundary para nosotros (realmente el valor del boundary no se usa, es solo para separar los campos) y como se genera un Content-Disposition. Es pues una petición equivalente a usar un <input type=”file” /> (cuyo atributo name fuese “image”).

Recibir el fichero en WebApi

Para recibir el fichero subido, necesitamos una acción de un controlador WebApi y usar un MultipartFormDataStreamProvider para guardar el fichero en disco:

  1. var streamProvider = new MultipartFileStreamProvider(uploadFolder);
  2. await Request.Content.ReadAsMultipartAsync(streamProvider);

Este código ya guarda el fichero en el disco. La carpeta usada es la especificada en la variable uploadFolder. De hecho si hubiese varios ficheros subidos a la vez, este código los guarda todos.

En mi caso he enviado una petición con un Content-Disposition cuyo nombre de fichero es 02.png, así que lo suyo sería esperar que en la carpeta especificada por uploadFolder hubiese este fichero. Pero no vais a encontrar ningún fichero llamado así. Por diseño WebApi ignora el valor de Content-Disposition (por temas de seguridad). En su lugar os vais a encontrar con un fichero (o varios) llamados BodyPart y un guid:

image

Por suerte para hacer que WebApi tenga en cuenta el valor del campo Content-Disposition y guarde el fichero con el nombre especificado basta con heredar de MultipartFormDataStreamProvider y reimplementar el método GetLocalFileName:

  1. class MultipartFormDataContentDispositionStreamProvider : MultipartFormDataStreamProvider
  2. {
  3.     public MultipartFormDataContentDispositionStreamProvider(string rootPath) : base(rootPath)
  4.     {
  5.     }
  6.     public MultipartFormDataContentDispositionStreamProvider(string rootPath, int bufferSize) : base(rootPath, bufferSize)
  7.     {
  8.     }
  9.     public override string GetLocalFileName(HttpContentHeaders headers)
  10.     {
  11.         if (headers.ContentDisposition != null)
  12.         {
  13.             return headers.ContentDisposition.FileName;
  14.         }
  15.         return base.GetLocalFileName(headers);
  16.     }
  17. }

Ahora en el controlador instanciamos un objeto MultipartFormDataContentDispositionStreamProvider en lugar del MultipartFormDataStreamProvider y ahora ya se nos guardarán los ficheros con los nombres especificados. Ojo, recuerda que WebApi no hace eso por defecto por temas de seguridad, así que si implementas esta solución valida los nombres de fichero que te envía el cliente.

¡Y ya está! La verdad es que el modelo de WebApi es radicalmente distinto al de ASP.NET MVC pero igual de sencillo y efectivo ;-)

Saludos!

Publicado por Eduard Tomàs i Avellana con no comments
Archivado en:

Angular y React (1/n): Empezando

¡Buenas! Empiezo con esta una serie de posts, que no se lo larga que será, comparando (en el buen sentido, nada de buscar un ganador ni un perdedor) un poco Angular con React.

Antes que nada el típico disclaimer: Angular y React no cubren los mismos aspectos del desarrollo web. Sí Angular cubre todo el espectro MVC, MVVM o como quieras llamarlo, React cubre solo la V: las vistas. Es pues, de funcionalidad más limitada que Angular. Así React puede combinarse con otras librerías para obtener un framework MVC completo. Hay quien lo ha hecho con Backbone (lógico, Backbone se puede combinar con cualquier cosa) pero lo habitual es hacerlo con Flux. Pero bueno… hasta hay quien lo ha hecho con… ¡Angular!

Si no conoces nada de Angular, tranquilo que durante esta serie de posts, iremos explicando lo que sea necesario y lo mismo aplica a React. De todos modos para Angular Xavier Jorge Cerdá y Pedro Hurtado están escribiendo un tutorial en Louesfera. Échale un vistazo. De React cuesta mucho más encontrar documentación en formato tutorial en castellano…

Hello world

Por supuesto, vamos a empezar con el Hello World, un ejemplo que generalmente es tan soso que no dice nada sobre lo que se quiere analizar, pero bueno… las tradiciones son tradiciones.

Ahí va el Hello World de Angular:

  1. <!DOCTYPE html>
  2. <html ng-app="hello-app">
  3. <head lang="en">
  4.     <meta charset="UTF-8">
  5.     <title>Hello Angular</title>
  6.     <script src="bower_components/angular/angular.js"></script>
  7.     <script>
  8.           angular.module('hello-app', []).controller('HelloController',function HelloController($scope) {
  9.             $scope.name = "eiximenis";
  10.         });
  11.     </script>
  12. </head>
  13. <body>
  14.     <div ng-controller="HelloController">
  15.         Hello {{name}}
  16.     </div>
  17. </body>
  18. </html>

Al ejecutar esta página se mostrará “Hello eiximenis” por pantalla.

Es un código muy simple pero sirve para ver tres de las características fundamentales de Angular:

  1. En Angular las vistas son HTML. Es decir están formadas por el propio DOM de la página. Puede parecer obvio (HTML va muy bien para definir aspecto visual) pero bueno, hay otras librerías (p. ej. Backbone) que usan código para definir las vistas.
  2. Hay un sistema de bindings para transferir datos desde el controlador (HelloController) hacia la vista (DOM). En este caso hemos usado la sintaxis más simple, al estilo mustache.
  3. El sistema de inyección de dependencias que tiene Angular (el parámetro $scope) está inyectado automáticamente por Angular. La idea es que se pueden inyectar aquellos servicios que se desee a los controladores.

Veamos el código equivalente en React:

  1. <!DOCTYPE html>
  2. <html>
  3. <head lang="en">
  4.     <meta charset="UTF-8">
  5.     <title>Hello react</title>
  6.     <script src="bower_components/react/react.js"></script>
  7.     <script src="bower_components/react/JSXTransformer.js"></script>
  8. </head>
  9. <body id="example">
  10.     <script type="text/jsx">
  11.         /** @jsx React.DOM */
  12.         React.renderComponent(
  13.         <div>Hello, eiximenis!</div>,
  14.         document.getElementById('example')
  15.         );
  16.     </script>
  17. </body>
  18. </html>

El ejemplo es muy simple, pero luce más complejo que el equivalente de Angular. Y eso es debido a la filosofía de React:

  • React se basa en el uso de pequeños componentes que cada uno de ellos se puede renderizar independientemente (algo parecido a lo que proponen librerías como Polymer, pero con la diferencia de que Polymer se basa en Web Components y extiende el DOM, mientras que React se basa en JavaScript y se olvida del DOM).
  • Las vistas no son HTML (DOM), si no clases JavaScript. La idea es la misma que las vistas de Backbone, con la salvedad de que en Backbone se suele interaccionar con el DOM (usualmente a través de jQuery) y en React no.
  • React “extiende” JavaScript para permitir esta mezcla de html y JavaScript. Para ello se usa un parser específico, que está en el fichero JSXTransformer.js
  • No hay binding en React, porque no hay DOM hacia el que enlazar nada.
  • React tiene el concepto de “DOM Virtual”: no se interacciona nunca con el DOM real, si no con un “DOM Virtual” proporcionado por React. Luego es React quien se encarga de sincronizar este “DOM Virtual” con el DOM real del navegador. Eso es un “epic win” para SEO en SPA: react puede renderizar vistas sin necesidad de un navegador… se puede renderizar una vista React desde node p. ej. usando el mismo código en el servidor que en el cliente, y permitiendo así que los buscadores indexen nuestro sitio. Esto no es posible en Angular, ya que Angular está atado al DOM y el DOM requiere un navegador. Ojo, no digo que con Angular no se pueda generar aplicaciones SPA con buen soporte para SEO, solo digo que con React el mismo código sirve para renderizar en servidor y en cliente, mientras que con Angular la parte del servidor requiere código adicional. Además, teóricamente, este “DOM Virtual” permitiría a React generar otras cosas que no sean vistas en HTML… quien sabe.

Bueno… y para ser el primer post lo dejaremos aquí…

Publicado por Eduard Tomàs i Avellana con 2 comment(s)
Archivado en:

ASP.NET MVC–Traduciendo las validaciones de CompareAttribute

Muy buenas! Seguimos ese tour de force sobre las traducciones de los mensajes de validación de Data Annotations.

En el primer post de esta serie vimos como crear adaptadores de atributos para permitirnos fácilmente y a nivel centralizado establecer las propiedades ErrorMessageResourceName y ErrorMessageResourceType.

El post terminaba con una lista de los distintos adaptadores que tiene ASP.NET MVC y que podíamos usar como clases base. Hay adaptadores definidos para casi todos los atributos de Data Annotations (Required, StringLength,…) pero no hay ninguno para el CompareAttribute. El atributo Compare no se usa mucho, ya que valida que dos propiedades tengan el mismo valor. El clásico uso es en formularios de registro donde el usuario debe introducir una contraseña dos veces para evitar que haya ningún error.

Pero el hecho de que ASP.NET MVC no incluya ningún adaptador base para dicho atributo no nos impide crearnos el nuestro y aplicar la misma técnica para traducir los mensajes de validación de dicho atributo. Para ello derivaremos de la clase DataAnnotationsModelValidator<T> (siendo T el tipo del atributo, en este caso el CompareAttribute).

La principal diferencia es que ahora debemos generar las validaciones de cliente, sobreescribiendo el método GetClientValidationRules().

Pese a todo, el código sigue siendo muy sencillo:

  1. public class LocalizedCompareAdapter : DataAnnotationsModelValidator<System.ComponentModel.DataAnnotations.CompareAttribute>
  2. {
  3.     public LocalizedCompareAdapter(ModelMetadata metadata, ControllerContext context, CompareAttribute attribute)
  4.         : base(metadata, context, attribute)
  5.     {
  6.         attribute.ErrorMessageResourceName = "Compare";
  7.         attribute.ErrorMessageResourceType = typeof(Resources.Messages);
  8.     }
  9.  
  10.     public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
  11.     {
  12.         var other = Attribute.OtherProperty;
  13.         return new[] { new ModelClientValidationEqualToRule(base.ErrorMessage, other) };
  14.     }
  15. }

En el método GetClientValidationRules devolvemos las validaciones de cliente. En este caso, queremos comparar dos propiedades así que devolvemos una ModelClientValidationEqualToRule, a la cual le pasamos el nombre de la otra propiedad. Recuerda que el [Compare] se aplica a una  propiedad (p. ej. ConfirmPassword) y se coloca el nombre de la otra propiedad (p. ej. Password):

  1. public string Password { get; set; }
  2. [Compare("Password")]
  3. public string ConfirmPassword { get; set; }

En este caso es la propiedad ConfirmPassword la que está decorada con el CompareAttribute, por lo tanto esta es la contendrá la regla de validación en cliente. De hecho el código HTML generado por los helpers Html.TextBoxFor para esas dos propiedades es:

  1. <input id="Password" name="Password" type="text" value="" />
  2. <input data-val="true" data-val-equalto="Los valores de ConfirmPassword y Password deben ser iguales"
  3.        data-val-equalto-other="Password" id="ConfirmPassword" name="ConfirmPassword" type="text" value="" />

Se puede ver que el <input /> que se corresponde a Password no tiene validaciones aplicadas y que es el <input /> que corresponde a ConfirmPassword el que tiene las validaciones de cliente aplicadas.

Podemos ver que el mensaje de error (data-val-equalto) está en castellano porque en mi fichero de recursos (Messages.es.resx) tengo la entrada “Compare”que es la que usa nuestro adaptador de recursos:

image

¡Y listos! Hemos visto como el hecho de que ASP.NET MVC  no provea un adaptador base no nos impide usar DataAnnotationsModelValidator<T> para crearnos nuestro propio adaptador, con la salvedad de que debemos indicar las validaciones de cliente a generar (las de servidor no son necesarias).

PD: Por supuesto debemos registrar ese adaptador de atributo como vimos en el post dedicado a los adaptadores:

  1. DataAnnotationsModelValidatorProvider.RegisterAdapter(
  2.     typeof (System.ComponentModel.DataAnnotations.CompareAttribute),
  3.     typeof (LocalizedCompareAdapter));

Un saludo!

Publicado por Eduard Tomàs i Avellana con no comments
Archivado en:

ASP.NET MVC–Traduciendo las validaciones implícitas

En el post anterior vimos como localizar los mensajes de validación de Data Annotations en ASP.NET MVC usando adaptadores de atributos. Pero además de esos mensajes ASP.NET MVC tiene algunos mensajes de traducción implícitos.

P. ej. si tenemos una propiedad de tipo Int y le intentamos poner un valor no numérico ASP.NET MVC mostrará un mensaje de error. Este mensaje de error no proviene de Data Annotations, por lo que no podemos usar la técnica descrita en el post anterior para traducirlo.

Vamos a ver como traducir los mensajes implícitos. Para ello basta con crear un fichero de recursos (en App_GlobalResources) con la entrada PropertyValueInvalid y MVC usará dicho mensaje para mostrar el error. Puedes usar {0} para mostrar el valor inválido y {1} para mostrar el nombre de la propiedad.

La entrada PropertyValueInvalid la usa el model binder cuando se encuentra un valor inválido para una propiedad. P. ej. si tenemos una propiedad definida como int e intentamos asignarle una cadena.

Debemos configurar el model binder para que use el fichero de recursos que hemos creado usando la propiedad ResourceClassKey:

  1. DefaultModelBinder.ResourceClassKey = "Messages";

El valor de ResourceClassKey es el nombre del fichero de recursos a usar.

Traducir el mensaje implícito de propiedad requerida

El atributo [Required] de Data Annotations nos permite especificar que el valor de una propiedad es obligatorio. Pero MVC trata automáticamente algunos campos como obligatorios: las propiedades cuyo tipo es un tipo por valor son tratadas como obligatorias automáticamente. Eso es lógico: si tengo una propiedad declarada como int, es obvio que debe tener siempre un valor, ya que int no admite el valor null.

Pero el tratamiento exacto que se da en esos casos depende del valor de la propiedad AddImplicitRequiredAttributeForValueTypes de la clase DataAnnotationsModelValidatorProvider:

  • Si vale true (valor por defecto) se añade automáticamente un atributo [Required] a cada propiedad cuyo tipo sea un tipo por valor.
  • Si vale false el tratamiento lo realiza el default model binder.

Si estamos en el primer caso, eso significa que dicho mensaje realmente tenemos que tratarlo a través de un adaptador de atributo porque, a todos los efectos, es como si la propiedad tuviese un atributo [Required] colocado.  Así nos podríamos crear un adaptador para el atributo Required, tal y como se comentaba en el post anterior.

Si estamos en el segundo caso, entonces es el model binder quien tratará ese caso. Para mostrar el mensaje (que por defecto es un insulso “A value is required”) usará la entrada PropertyValueRequired del fichero de recursos que hayamos especificado mediante la propiedad ResourceClassKey.

Así vemos que el DefaultModelBinder usa dos claves del fichero de recursos:

  • PropertyValueInvalid: Cuando se asigna un valor incorrecto a una propiedad
  • PropertyValueRequired: Cuando no se asigna valor a una propiedad cuyo tipo es un tipo por valor (siempre y cuando AddImplicitRequiredAttributeForValueTypes valga false).

Nota: Esas dos son las dos únicas claves que usa el Default Model Binder.

Validación en cliente

Si tienes habilitada la validación en cliente, las cosas se ponen un poco más interesante. Hasta ahora hemos visto como traducir los mensajes implícitos que usa el Default Model Binder, pero la validación en cliente es independiente del Model Binder y es necesario un paso más.

Si la tienes habilitada verás que no te funciona… Aparecen otros mensajes de errores. P. ej. en el caso de introducir un valor no numérico en una propiedad numérica (un int p. ej.) ahora aparece un mensaje “The field xxx must be a number”.

Mirando el código fuente de la página puedes ver que este mensaje es de la validación en cliente:

image

¿Quien ha generado este mensaje y de donde lo saca?

Pues bien, estos mensajes de errores los genera la clase ClientDataTypeModelValidatorProvider que es la encargada de gestionar las validaciones en cliente.

Por suerte dicha clase expone también una propiedad ResourceCssKey, al igual que el DefaultModelBinder que podemos usar para especificarle un fichero de recursos:

  1. ClientDataTypeModelValidatorProvider.ResourceClassKey = "Messages";

Ahora la pregunta a responder es: ¿qué claves espera encontrar en este fichero de recursos?

Pues las siguientes:

  • FieldMustBeNumeric: En el caso que se entre una cadena en campos que deban ser numéricos
  • FieldMustBeDate: En el caso de que se entre una cadena incorrecta en campos que deban ser fechas.

Si te preguntas “qué es un campo numérico”, pues cualquiera cuyo tipo en la propiedad .NET equivalente sea: byte, sbyte, short, ushort, int, uint, long, ulong, float, double y decimal o las versiones Nullable de esos tipos.

Un campo fecha es aquel cuya propiedad se haya declarado como DateTime (o DateTime?) (y que no tenga aplicado el atributo [DataType] con el valor “Time”).

Por lo tanto nos basta con agregar esas dos recursos a nuestro fichero de recursos para poder traducir los mensajes implícitos.

Vale. Un apunte final: si te fijas en el código HTML para el <input /> de la propiedad Age, verás que no ha generado el data-val-required. Eso es porque tenia la línea:

  1. DataAnnotationsModelValidatorProvider.
  2.     AddImplicitRequiredAttributeForValueTypes = false;

Recuerda que eso hace que MVC no añada automáticamente un [Required]. La validación en cliente no entiende de campos requeridos si no hay un [Required]. Es por ello que no se genera el código en cliente para asegurarse que deba entrar un valor en este campo. Si elimino esa línea y ejecuto de nuevo ahora si que me aparece la validación de campo obligatorio:

image

Si te preguntas ahora de donde sale el mensaje usado para la validación de campo obligatorio en el cliente, la respuesta es que del atributo [Required] que MVC ha añadido automáticamente a la propiedad.

Por lo tanto, si usas un adaptador de atributo para el [Required] este se aplicará (y dado que se aplica también en servidor verás el mismo mensaje en el cliente que en el servidor).

En resumen…

Resumiendo, los mensajes implícitos de MVC son generados por:

  1. El DefaultModelBinder en el servidor
  2. El ClientDataTypeModelValidatorProvider en la validación en cliente

Ambos usan una propiedad (ResourceCssKey) para especificar el fichero de recursos a utilizar. Y dentro de ese fichero de recursos podemos colocar las claves:

  1. PropertyValueInvalid: Cuando se asigna un valor inválido a una propiedad. Usado por el DefaultModelBinder
  2. PropertyValueRequired: Cuando no se ha informado el valor de una propiedad que es obligatoria porque su tipo es un tipo por valor (usado por el DefaultModelBinder solo si AddImplicitRequiredAttributeForValueTypes  es false).
  3. FieldMustBeNumeric: Cuando se introduce un valor no numérico en una propiedad numérica (usada por ClientDataTypeModelValidatorProvider. El equivalente en servidor sería PropertyValueInvalid).
  4. FieldMustBeDate: Cuando se introduce un valor que no es una fecha en una propiedad de tipo fecha (usada por ClientDataTypeModelValidatorProvider. El equivalente en servidor sería PropertyValueInvalid).

Espero que te haya sido útil! En otro post iremos un paso más allá y veremos como personalizar al máximo esos mensajes de error. Pero ya te avanzo que nos meteremos hasta la cocina y el baño de ASP.NET MVC.

Saludos!

Publicado por Eduard Tomàs i Avellana con no comments
Archivado en:

ASP.NET MVC–Traducir los mensajes de error de DataAnnotations… otra vez.

Pues sí… la verdad es que esa es una cuestión recurrente en ASP.NET MVC. Y es que con las distintas versiones de MVC han aparecido distintas maneras de conseguir este propósito.

Nota 1: Para tener una idea de como eran las cosas en MVC2 echad un vistazo al post que publicó el Maestro hace tiempo: Modificar los mensajes de validación por defecto en ASP.NET MVC 2. Por favor léete dicho post, pues en cierto modo mi post es una “continuación”.

Nota 2: Una opción rápida es instalar paquetes de idioma de MVC. Esos paquetes vienen con los mensajes ya traducidos en varios idiomas. Podemos instalar tantos paquetes de idiomas como necesitemos y dependiendo de la cultura en el hilo del servidor se usará uno u otro. Eso nos permite tener los mensajes traducidos (aunque no podremos modificarlos, son los que son). De nuevo el Maestro publicó sobre ello: Errores de ASP.NET MVC 4 en distintos idiomas

El post de José María explicar muy bien como era la situación en MVC2. Pero en MVC3 y sobretodo en MVC4 hubieron algunos cambios significativos que voy a comentar en este post.

Por supuesto podemos seguir usando la propiedad ErrorMessage de los atributos de Data Annotations. Pero eso sigue sin ser multi-idioma y además es muy pesado. Otra opción que sigue siendo válida y de hecho es la que se sigue (indirectamente) usando son las propiedades ErrorMessageResourceName y ErrorMessageResourceType.

Herencia de atributos

Jose María menciona en su post la posibilidad de usar esas propiedades a cada atributo de DataAnnotations (lo que no es muy DRY) o bien crearse un atributo de DataAnnotations derivado que auto-asigne dichas propiedades. Es decir hacer algo como lo siguiente:

  1. public class LocalizedRangeAttribute : RangeAttribute
  2. {
  3.  
  4.     public LocalizedRangeAttribute(int min, int max) : base(min, max)
  5.     {
  6.         InitProps();
  7.     }
  8.  
  9.     public LocalizedRangeAttribute(double min, double max) : base(min, max)
  10.     {
  11.         InitProps();
  12.     }
  13.  
  14.     public LocalizedRangeAttribute(Type type, string min, string max) : base(type, min, max)
  15.     {
  16.         InitProps();
  17.     }
  18.  
  19.     private void InitProps()
  20.     {
  21.         ErrorMessageResourceName = "Range";
  22.         ErrorMessageResourceType = typeof (Resources.Messages);
  23.     }
  24. }

Por supuesto me he creado mi fichero Resources.resx dentro de la carpeta App_GlobalResources (tal y como cuenta José María en su post):

image

Lamentablemente eso rompe la validación en cliente de MVC3. A modo de ejemplo tengo dicha entidad, con dos propiedades, una decorada con el Range de toda la vida y otra con mi LocalizedRange:

  1. public class Ufo
  2. {
  3.     [Range(1,90)]
  4.     public int Age { get; set; }
  5.  
  6.     [LocalizedRange(1,90)]
  7.     public int LocalizedAge { get; set; }
  8. }

Creo una vista estándar para editar objetos de este modelo:

  1. @model WebApplication1.Models.Ufo
  2.  
  3.  
  4. @Html.LabelFor(m=>m.Age)
  5. @Html.TextBoxFor(m=>m.Age)
  6. <br />
  7. @Html.LabelFor(m => m.LocalizedAge)
  8. @Html.TextBoxFor(m => m.LocalizedAge)

Si nos vamos al código fuente de la página veremos lo siguiente:

  1. <label for="Age">Age</label>
  2. <input data-val="true" data-val-number="The field Age must be a number." data-val-range="The field Age must be between 1 and 90." data-val-range-max="90" data-val-range-min="1" data-val-required="The Age field is required." id="Age" name="Age" type="text" value="" />
  3. <br />
  4. <label for="LocalizedAge">LocalizedAge</label>
  5. <input data-val="true" data-val-number="The field LocalizedAge must be a number." data-val-required="The LocalizedAge field is required." id="LocalizedAge" name="LocalizedAge" type="text" value="" />

Observa como el segundo input, que se corresponde a la propiedad LocalizedAge (decorada con mi LocalizedRangeAttribute) no tiene los atributos para validar en cliente el rango (los data-val-range-*). Por lo tanto la validación en cliente de dicho campo no funcionará.

En servidor por supuesto la validación funcionará y además se puede ver que en el segundo caso se usa el mensaje del fichero de recursos:

image

De todos aunque la herencia funcionase bien existen motivos para no usarla (p. ej. si quieres enviar a una vista una entidad de EF, deberías decorar dicha entidad con los atributos heredados, lo que no es muy bonito y no sé si puede causar efectos colaterales en el propio EF).

Adaptadores de atributos

Vale, queda claro que la herencia de atributos no funciona bien con la validación remota. Pero que no cunda el pánico, ASP.NET MVC nos da otro mecanismo: los adaptadores de atributos.

Para crear una adaptador de atributo, debemos derivar de una clase de MVC, que depende del tipo de atributo. P. ej. para crear un adaptador para el atributo de Range, debemos derivar de System.Mvc.RangeAttributeAdapter:

  1. public class LocalizedRangeAttributeAdatper : RangeAttributeAdapter
  2. {
  3.     public LocalizedRangeAttributeAdatper(ModelMetadata metadata, ControllerContext context, RangeAttribute attribute) : base(metadata, context, attribute)
  4.     {
  5.         attribute.ErrorMessageResourceName = "Range";
  6.         attribute.ErrorMessageResourceType = typeof(Resources.Messages);
  7.     }
  8. }

El adaptador recibe en su constructor al propio RangeAttribute y allí aprovechamos para establecer las propiedades ErrorMessageResourceName y ErrorMessageResourceType.

Una vez hemos creado el adaptador, debemos declararlo en ASP.NET MVC. Para ello en el Application_Start metemos el siguiente código:

  1. DataAnnotationsModelValidatorProvider.
  2.     RegisterAdapter(typeof (RangeAttribute), typeof (LocalizedRangeAttributeAdatper));

La llamada RegisterAdapter acepta dos parámetros: el tipo del atributo a adaptar y el tipo del adaptador. Una vez hecho esto, automáticamente todos los atributos Range pasarán a usar, los recursos indicados. Ya no hay necesidad ninguna del LocalizedRange.

Otros adaptadores de atributos son los siguientes (todos en el namespace System.Web.Mvc):

image

¡Espero que os haya sido útil!

Saludos!

Publicado por Eduard Tomàs i Avellana con 3 comment(s)
Archivado en:

C#- Vitaminiza tus enums con métodos de extensión

Buenas, un post cortito y sencillito ;)

En C# los enums son relativamente limitados: básicamente se limitan a tener un conjunto de valores y nada más. En otros lenguajes como Java o Swift, los enums pueden declarar métodos.

A priori puede parecer que no es muy necesario que un enum tenga un método, y de hecho no es algo que se suela echar en falta. Pero en algunos casos puede ser útil, especialmente para tener nuestro código más bien organizado.

P. ej. imagina un enum que contuviese los valores de los puntos cardinales:

  1. public enum FacingOrientation
  2. {
  3.     North = 0,
  4.     East = 1,
  5.     South = 2,
  6.     West = 3
  7. }

Ahora podríamos requerir un método que nos devolviese el siguiente punto cardinal, en sentido horario. Es decir si estamos mirando al norte y giramos en sentido horario, estaremos mirando al este.

Este método sería un candidato para estar en el propio enum para que así pudiese hacer tener código como el siguiente:

  1. var orientation = FacingOrientation.South;
  2. var neworientation = orientation.Turn(1);

El método Turn devolvería la nueva orientación después de N giros en sentido horario.

Como he dicho antes en C# esto no es directamente posible porque los enums no pueden contener métodos. Pero por suerte si que podemos declarar un método de extensíón sobre un enum específico:

  1. public static FacingOrientation Turn(this FacingOrientation orientation, int steps)
  2. {
  3.     var idx = (int)orientation;
  4.     idx += steps;
  5.     return (FacingOrientation)(idx % 4);
  6. }

Y el resultado es a todos los efectos casi idéntico :)

Saludos!

Publicado por Eduard Tomàs i Avellana con 2 comment(s)
Archivado en:

ASP.NET MVC–Vigila los nombres de los parámetros de tus acciones

Muy buenas! Un post cortito para contaros un problemilla que nos hemos encontrado en un proyecto ASP.NET MVC5. Aunque seguro que aplica a todas las versiones de MVC desde la 2 al menos.

Es uno de aquellos casos en que, evidentemente hay algo que está mal, pero a simple vista todo parece correcto. Luego das con la causa del error puede que o bien no entiendas el porqué o bien digas “¡ah claro!” dependiendo de si conoces o no como funciona el Model Binder por defecto de MVC.

Reproducción del error

Es muy sencillo. Create una clase llamada Beer tal y como sigue:

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

En una aplicación real, para editar una cerveza quizá usaríamos un viewmodel que contendría la cerveza que estamos editando y datos adicionales, p. ej. una lista con los tipos válidos para que el usuario pueda seleccionarlos de una combo:

  1. public class BeerViewModel
  2. {
  3.     public Beer Beer { get; set; }
  4.     public SelectList BeerTypes { get; set; }
  5. }

En el controlador rellenaríamos un BeerViewModel y lo mandaríamos para la vista de edición:

  1. public ActionResult Edit(int id)
  2. {
  3.     // Cargaramos la cerveza de la BBDD
  4.     var beer = new Beer() {Id = id, Name = "Beer " + id};
  5.     var model = new BeerViewModel()
  6.     {
  7.         Beer = beer,
  8.         // Cargaramos los tipos de cerveza de algn sitio
  9.         BeerTypes = new SelectList(new[]
  10.         {
  11.             new {Id = 1, Name = "Pilsen"},
  12.             new {Id = 2, Name = "Bock"},
  13.             new {Id = 3, Name = "IPA"}
  14.         }, "Id", "Name")
  15.     };
  16.     return View(model);
  17. }

La vista de edición por su parte se limita a mostrar un formulario para editar el nombre y el tipo de cerveza:

  1. @model WebApplication17.Models.BeerViewModel
  2.            
  3. <h2>Edit a Beer</h2>
  4.  
  5. @using (Html.BeginForm())
  6. {
  7.     @Html.LabelFor(m => m.Beer.Name)
  8.     @Html.TextBoxFor(m => m.Beer.Name)
  9.     <br />
  10.     <p>Choose beer type:
  11.         @Html.DropDownListFor(m => m.Beer.BeerTypeId, Model.BeerTypes)
  12.     </p>
  13.     <input type="submit" value="edit"/>
  14. }

El funcionamiento de la vista es, como era de esperar, correcto:

image

Ahora creamos la acción para recibir los datos de la cerveza y miramos que datos recibimos en el controlador:

image

¡No se ha producido el binding! Los datos que envía el navegador en el POST son correctos (no podía ser de otra forma ya que he usado los helpers para formulario):

image

¿Cuál es la causa del fallo?

Pues que el parámetro de la acción se llama “beer”. Cámbialo para que tenga otro nombre y… voilá:

image

Todos los datos enlazados (excepto el Id vale, al final lo comentamos).

¿Porque no puede mi parámetro llamarse beer?

Porque el ViewModel que estamos usando BeerViewModel tiene una propiedad con ese nombre. De hecho si cambias el nombre de la propiedad del BeerViewModel te funcionará todo de nuevo. Y eso tiene que ver en como funciona el Model Binder. Déjame que te lo cuente de forma simplificada para que tengas clara el porque eso falla.

El Model Binder es el encargado de enlazar los valores de la request con los parámetros del controlador. Los parámetros que recibe el Model Binder de la request (en el form data) son los siguientes:

Beer.Name y Beer.BeerTypeId

Cuando el Model Binder va a enlazar Beer.Name hace lo siguiente:

  • Dado que “Beer.Name” tiene un punto el model binder busca si existe algún parámetro en el controlador llamado “Beer” (case insensitive). Esto es porque un controlador puede tener varios parámetros en la acción.
    • Si lo encuentra entonces buscará una propiedad que se llame Name en dicho parámetro y la enlazará.
    • Si no lo encuentra buscará el primer parámetro posible que tenga una propiedad llamada Beer.
      • Dentro de la propiedad llamada Beer buscará una propiedad llamada Name para enlazarla.

Por eso cuando en la acción el parámetro se llamaba “beer”, el Model Binder al enlazar el parámetro “Beer.Name” intentaba enlazar la propiedad “Name” del propio parámetro. Pero esa propiedad no existe. La clase BeerViewModel solo tiene una propiedad “Beer” y otra “BeerTypes”. Lo mismo ocurre con el parámetro Beer.BeerTypeId (intenta enlazar la propiedad BeerTypeId del propio BeerViewModel).

Al final el Model Binder encuentra que no hay nada que enlazar, así que no hace nada y por eso no recibimos datos.

Cuando hemos cambiado el parámetro del controlador para que se llame “data” entonces el Model Binder al enlazar “Beer.Name” busca un parámetro llamado “beer” en la acción. Pero como NO lo encuentra, entonces busca un parámetro en el controlador que tenga una propiedad llamada “Beer”. Y lo encuentra, porque el parámetro “data” (el BeerViewModel) tiene una propiedad llamada Beer. Luego busca si el tipo de dicha propiedad (la clase Beer) tiene una propiedad llamada Name. Y la encuentra, y la enlaza.

Por eso en el segundo caso recibimos los datos.

Bonus track: ¿Por qué el Id no se enlaza? Esa es sencilla: porque el route value se llama “id” (el POST está hecho a /Beers/Edit/{id}. El Model Binder soporta enlazado de route values, pero el nombre id no lo puede enlazar porque:

  1. No existe ningún parámetro llamado Id en la acción
  2. Ningún parámetro de la acción tiene una propiedad llamada Id.

Espero este post os haya sido interesante y si alguna vez os pasa eso… pues bueno, ya sabéis la razón! :D

Saludos!

Publicado por Eduard Tomàs i Avellana con no comments
Archivado en:

Código sin nulls

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!

Publicado por Eduard Tomàs i Avellana con no comments
Archivado en:

ASP.NET vNext–Crea tus propios “comandos K”

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!

Publicado por Eduard Tomàs i Avellana con 1 comment(s)
Archivado en:

ASP.NET MVC6 (vNext)–ViewComponents

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 :)

Creando tu librería JavaScript con requirejs y gulp

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

image

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

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

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

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

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

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

image

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

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

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

¡Un saludo!

Publicado por Eduard Tomàs i Avellana con no comments
Archivado en:

ASP.NET vNext–Model Binding

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

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

Pruebas de caja negra

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

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

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

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

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

Las clases Customer y Product contienen simplemente propiedades:

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

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

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

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

image

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

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

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

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

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

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

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

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

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

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

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

Atributo [FromBody]

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

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

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

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

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

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

Y el resultado ha sido muy interesante:

image

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

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

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

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

image

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

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

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

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

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

Por defecto vNext viene con los siguientes Value Providers:

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

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

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

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

Y hago la siguiente petición?

C:\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!

Más artículos Página siguiente >