This page looks best with JavaScript enabled

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

 ·  ☕ 4 min  ·  ✍️ eiximenis

Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí

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">Agelabel>
  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">LocalizedAgelabel>
  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!

Si quieres, puedes invitarme a un café xD

eiximenis
ESCRITO POR
eiximenis
Compulsive Developer