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:xxxbinDebug se mapeará a c:xxxbinDebugApp_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!

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!

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.

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!

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í…

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!

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!

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!

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!

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! 😀

Saludos!