sábado, 21 de marzo de 2015

ASP.NET. Localizando Data Annotations (I) - Archivos de recursos

El espacio de nombres System.ComponentModel.DataAnnotations proporciona clases de atributos que se usan para definir los metadatos para ASP.NET MVC y los controles de ASP.NET.

Estos atributos nos permiten especificar las cadenas a utilizar en la interfaz de la aplicación cuando se hace referencia al campo, se detectan errores de validación, etc. Esto resulta muy útil para tener definidas estas cadenas en un único lugar de la aplicación y que los mensajes al usuario sean uniformes a lo largo de toda nuestra aplicación.

Sin embargo, en este mundo cada vez más globalizado, cada vez son menos las aplicaciones cuyo interfaz se define en un único idioma. Entonces, ¿cómo utilizamos los atributos del namespace DataAnnotations de forma que nuestros textos se adapten al idioma del usuario?



En la serie de artículos que comienzo aquí trataré de explicar las diferentes opciones que tenemos a la hora de abordar este problema.

Creando la aplicación de ejemplo


Para mostrar las diferentes soluciones al problema vamos a crear un nueva Aplicación ASP.NET LocalizeDataAnnotations en nuestro Visual Studio, seleccionando la plantilla vacía y marcando el check para incluir las referencias a MVC.




Vamos a crear una nueva Interfaz IPersona.cs/IPersona.vb en nuestra carpeta Model en la que definiremos las propiedades que tendrán los objetos de nuestro modelo. Como veremos, el sentido de crear esta interfaz es el de poder utilizar una misma Vista para todos los ejemplos.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LocalizeDataAnnotations.Models
{
    public interface IPersona
    {
        string Nombre { get; set; }
        string Apellido { get; set; }
        string Ciudad { get; set; }
        string Email { get; set; }
    }
}
Public Interface IPersona

    Property Nombre As String
    Property Apellido As String
    Property Ciudad As String
    Property Email As String

End Interface

A continuación crearemos una Vista Index en la carpeta Views/Home que utilizaremos para ver los resultados de nuestros ejemplos. Esta vista utilizará como Modelo una instancia de la interfaz IPersona y simplemente muestra un formulario que nos permite asignar valores a las propiedades y enviarlos al servidor para su validación:

@model LocalizeDataAnnotations.Models.IPersona

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        @using (Html.BeginForm())
        {
            @Html.EditorForModel()
            <br />
            <input type="submit" value="Validar" />
        }

    </div>
</body>

</html>
@ModelType LocalizeDataAnnotations.IPersona

@Code
    Layout = Nothing
End Code

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        @Using (Html.BeginForm())
            @Html.EditorForModel()
            @:<br />
            @:<input type="submit" value="Validar" />
        End Using

    </div>
</body>

</html>

Añadiendo los atributos DataAnnotations


Para poder probar nuestra vista y ver la funcionalidad de los atributos del Namespace DataAnnotations creamos una nueva clase Persona que implementa la interfaz IPersona en la carpeta Model. Para el ejemplo vamos a establecer el nombre de cada propiedad a través del atributo DisplayNameAttribute y dos atributos de validación: un RequiredAttribute con mensaje de error personalizado para establecer la propiedad Nombre como obligatoria, y un StringLengthAttribute para establecer que la longitud de la propiedad Ciudad debe estar entre 4 y 20 caracteres.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace LocalizeDataAnnotations.Models
{
    public class Persona: IPersona
    {

        [Required(ErrorMessage = "Debe introducir un nombre")]
        [DisplayName("Nombre")]
        public string Nombre { get; set; }

        [DisplayName("Apellido")]
        public string Apellido { get; set; }

        [DisplayName("Ciudad")]
        [StringLength(20, MinimumLength=4, ErrorMessage="El nombre de Ciudad debe tener una longitud entre 4 y 20 caracteres")]
        public string Ciudad { get; set; }

        [DisplayName("Correo electrónico")]
        public string Email { get; set; }

    }
}
Imports System.ComponentModel
Imports System.ComponentModel.DataAnnotations

Public Class Persona
    Implements IPersona

    <Required(ErrorMessage:="Debe introducir un nombre")> _
    <DisplayName("Nombre")> _
    Public Property Nombre As String Implements IPersona.Nombre

    <DisplayName("Apellido")> _
    Public Property Apellido As String Implements IPersona.Apellido

    <DisplayName("Ciudad")> _
    <StringLength(20, MinimumLength:=4, ErrorMessage:="El nombre de Ciudad debe tener una longitud entre 4 y 20 caracteres")> _
    Public Property Ciudad As String Implements IPersona.Ciudad

    <DisplayName("Correo electrónico")> _
    Public Property Email As String Implements IPersona.Email

End Class

Para finalizar y probar el ejemplo añadimos un controlador HomeController en la carpeta Controllers. En el controlador crearemos una única acción Index que recibe un parámetro del tipo Persona y lo pasa a nuestra vista Index.


using LocalizeDataAnnotations.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace LocalizeDataAnnotations.Controllers
{
    public class HomeController : Controller
    {

        public ViewResult Index(Persona persona)
        {
            return View(persona);
        }

    }
}
Imports System.Web.Mvc

Namespace Controllers
    Public Class HomeController
        Inherits Controller

        Function Index(persona As Persona) As ViewResult
            Return View(persona)
        End Function

    End Class
End Namespace

Al arrancar nuestra aplicación podremos ver en el navegador un formulario para introducir los valores de la clase Persona con etiquetas que toman el texto de los atributos DisplayNameAttribute al hacer click en el botón Validar la información se manda al servidor y, en caso de existir errores de validación nos aparecerán, tomando los textos de los mensajes de los atributos de validación.

Formulario de prueba

Usar archivos de recursos


Hemos definido los textos del modelo a partir de los atributos Data Annotations, ahora veremos qué podemos hacer para adaptar estos mensajes al idioma del usuario.

La solución más sencilla, y que viene ya implementada en los atributos, es la de usar archivos de recursos. Para nuestro ejemplo crearemos una carpeta Content y añadiremos dos archivos de recursos:
  • El archivo Textos.resx que tendrá los mensajes en español y que será el que utilice nuestra aplicación por defecto
  • El archivo Textos.en.resx que tendrá los mensajes en inglés y que utilizará nuestra aplicación en caso de que el usuario tenga definida una cultura con idioma inglés
No debemos olvidar establecer el Modificador de acceso de los archivos a Public.

Archivo de recursos en español

Archivo de recursos en inglés

He modificado algunos de los textos que utilizábamos en el ejemplo anterior para que se vea la diferencia cuando probemos la solución.

A continuación crearemos una nueva clase PersonaResource que herede de nuestra interfaz IPersona y en la que estableceremos los textos de los atributos a través de los archivos de recursos recién creados.


using LocalizeDataAnnotations.Content;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace LocalizeDataAnnotations.Models
{
    public class PersonaResource: IPersona
    {

        [Required(ErrorMessageResourceName = "NombreObligatorio", ErrorMessageResourceType = typeof(Textos))]
        [Display(Name="Nombre", ResourceType=typeof(Textos))]
        public string Nombre { get; set; }

        [Display(Name = "Apellido", ResourceType = typeof(Textos))]
        public string Apellido { get; set; }

        [Display(Name = "Ciudad", ResourceType = typeof(Textos))]
        [StringLength(20, MinimumLength = 4, ErrorMessageResourceName="CiudadErrorLongitud", ErrorMessageResourceType=typeof(Textos))]
        public string Ciudad { get; set; }

        [Display(Name = "Email", ResourceType = typeof(Textos))]
        public string Email { get; set; }

    }
}
Imports System.ComponentModel.DataAnnotations
Imports LocalizeDataAnnotations.My.Resources

Public Class PersonaResource
    Implements IPersona

    <Required(ErrorMessageResourceName:="NombreObligatorio", ErrorMessageResourceType:=GetType(Textos))> _
    <Display(Name:="Nombre", ResourceType:=GetType(Textos))> _
    Public Property Nombre As String Implements IPersona.Nombre

    <Display(Name:="Apellido", ResourceType:=GetType(Textos))> _
    Public Property Apellido As String Implements IPersona.Apellido

    <Display(Name:="Ciudad", ResourceType:=GetType(Textos))> _
    <StringLength(20, MinimumLength:=4, ErrorMessageResourceName:="CiudadErrorLongitud", ErrorMessageResourceType:=GetType(Textos))> _
    Public Property Ciudad As String Implements IPersona.Ciudad

    <Display(Name:="Email", ResourceType:=GetType(Textos))> _
    Public Property Email As String Implements IPersona.Email

End Class

Por último añadimos una nueva acción Resource a nuestro controlador HomeController que pasa un objeto del tipo PersonaResource a la vista Index.

        public ViewResult Resource(PersonaResource persona)
        {
            return View("Index", persona);
        }
        Function Resource(persona As PersonaResource) As ViewResult
            Return View("Index", persona)
        End Function

Para probar nuestra aplicación, establecemos la cultura del interfaz al idioma español en el archivo Web.config añadiendo la entrada globalization en la sección system.web.

  <system.web>
    <compilation debug="true" targetFramework="4.5"/>
    <httpRuntime targetFramework="4.5"/>
    <globalization culture="auto" uiCulture="es" />
  </system.web>

Ahora, si arrancamos la aplicación y accedemos a la ruta http://<rutapublicacion>/Home/Resource podremos ver como los textos han cambiado y ahora se muestran los del archivo de recursos.

Formulario de prueba archivo de recursos español

Si modificamos el Web.config para establecer la cultura del interfaz al idioma inglés, podremos ver cómo nuestra aplicación utiliza las cadenas en inglés.

  <system.web>
    <compilation debug="true" targetFramework="4.5"/>
    <httpRuntime targetFramework="4.5"/>
    <globalization culture="auto" uiCulture="en" />
  </system.web>

Formulario de prueba archivo de recursos inglés
En las próximas entradas abordaré otras posibles soluciones repasando los pros y los contras de cada una de ellas.

Continúa con:
ASP.NET. Localizando DataAnnotations (II) - Utilizar una plantilla T4 para conectar a cualquier origen
ASP.NET. Localizando Data Annotations (y III) - Personalizar Atributos de Validación

El código completo de los ejemplos mostrados a lo largo de los 3 artículos, tanto en C# como en VB.Net, puede descargarse de:

Códigos de muestra - Ejemplos MSDN

No hay comentarios:

Publicar un comentario