in Code & IT

Localizar Enumerações

Continuando no desafio de localizar a minha aplicação, estou agora a atacar as enumerações. Esta também é uma óptima oportunidade de efectuar algum refactoring às classes e estruturas existentes.

Parte das enumerações usadas são automaticamente escritas com uma estrutura que era óptima.. até agora. A aplicação é N-layer, dividido em camadas lógicas. Por enquanto, e porque ainda não foi necessário mais, mantém-se monotier. As camadas lógicas neste caso são UI, BLL (Business Lógic Layer) e DAL (Data Access Layer), mais os repositórios de dados. Comum a todas as camadas há duas bibliotecas – MAAPPFramework que é um conjuntos de classes base, interfaces base, e muitos helpers comuns a várias aplicações (O MAAPP significa Miguel Alho Application : D ) e a camada de BO (Business Objects) que inclui as classes conceptuais e DTOs (Data Transfer Objects) do domínio. Os objectos nesta biblioteca são “dummy objects”, no sentido que não tem funcionalidade (senão algumas ordenações e extensões). Apenas suportam dados e estão devidamente decoradas para serem serializadas. Esta biblioteca é transformada em dll e utilizada por todas as camadas. Esta arquitectura está muito bem exemplificado pelo Imar Spaanjars nos seus artigos Building Layered Web Applications with Microsoft ASP.NET 2.0.

As enumerações que eu tinha incluíam sempre um valor numérico definido explicitamente (evitando súbitas alterações de referências para com a base de dados numa nova geração de código) e uma descrição com texto legível, em vez de um texto colado em CamelCase. um exemplo:

namespace Namespace.BO
{
    [Serializable]  
    public enum EnumTipoContacto
    {	
        [Description("Telefone")]
	Telefone = 1,
        [Description("Telemóvel")]
        Telemovel = 2,
        [Description("Email")]
        Email = 3,
        [Description("Fax")]
        Fax = 4,
        [Description("Outro Tipo de Contacto")]
        Outro = 5
    }
}

A descrição escrita como atributo, recorrendo ao System.ComponentModel.DescriptionAttribute permite em runtime ter uma versão legível do nome da enumeração, óptima para apresentação na UI, em vez do tipo valor devolvido pelo método ToString(). No exemplo em cima, O caso do “Outro” demonstra a possível diferença. Para obter os valores, na minha framework tenho um método numa helper class que permite obter o valor da descrição, bem como uma extension method para ajudar:

public class EnumDescription
{
        public static string GetDescription(Enum value)
        {
            FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
            DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
            return (attributes.Length > 0) ? attributes[0].Description : value.ToString();
        }

        //(...)
}

public static class EnumExtension
{
    public static string GetDescription(this Enum e)
    {
         return EnumDescription.GetDescription(e);
    }
}

Considerando que o código é gerado a partir de um modelo que controlo, é uma óptima solução, desde que localizado a uma definição linguística. Seria possível criar múltiplos atributos (costumizados) para suportar descrições localizadas, por exemplo, mas a manutenção e separação não me parece optimizado. Procurei outra solução que me permitiria utilizar ficheiros de recursos (.resx).

Uma das técnicas que encontrei parece simples e óptima. A descrição passa a ser definida no ficheiro de recursos, e a chave é o nome qualificado da enumeração. Optei pelo nome completo porque é me fácil de gerar com T4. Assim, a minha enumeração fica simplificada:

[Serializable]
public enum EnumTipoContacto
{	
	Telefone = 1,
        Telemovel = 2,
        Email = 3,
        Fax = 4,
        Outro = 5,
}

e o meu ficheiro .resx terá um conjunto de chaves valores qualificados, que pode ser localizado na versão do ficheiro para outra língua:

(...)

    Telefone
  
 
    Telemóvel
  
 
    Email
  
 
    Fax
  
 
    Outro Tipo / Desconhecido
 
(...)

Para obter o valor da chave, a operação é ligeiramente diferente:

public static string GetLocalizedDescription(EnumTipoContacto tipo)
{
      ResourceManager resources = Namespace.BO.Properties.Enum.ResourceManager;

      string resourceKey = String.Format("{0}.{1}", tipo.GetType(), tipo);
      string localizedDescription = resources.GetString(resourceKey);

      if (localizedDescription.IsNullOrEmpty())
           return tipo.ToString();
      else
           return localizedDescription;
}

Neste exemplo, tipo.GetType() devolve Namespace.BO.EnumTipoContacto, permitindo construir a chave. é necessário ter em atenção que linha

ResourceManager resources = Namespace.BO.Properties.Enum.ResourceManager;

obriga-me a que a assemblagem gerada seja o Namespace.BO.dll e o ficheiro de recursos é o Enum.resx ou Enum.xx.resx onde xx representa o código cultural (“es”, por exemplo). Novamente, porque o meu código é em boa parte autogerado, tenho controlo sobre esse formato.

Outro método muito útil, para usar como DataSource para preencher DropDownLists e controlos do género é a seguinte:

public static List<KeyValuePair<string, string>> GetDDLList()
{
            List<KeyValuePair<string, string>> kvPairList = new List<KeyValuePair<string, string>>();
            ResourceManager resources = Namespace.BO.Properties.Enum.ResourceManager;

            foreach (Enum enumValue in Enum.GetValues(typeof(EnumTipoContacto)))
            {
                string resourceKey = String.Format("{0}.{1}", enumValue.GetType(), enumValue);
                string localizedDescription = resources.GetString(resourceKey);
                
                if(localizedDescription.IsNullOrEmpty())
                    kvPairList.Add(new KeyValuePair<string, string>(enumValue.ToString(), enumValue.ToString()));
                else
                    kvPairList.Add(new KeyValuePair<string, string>(enumValue.ToString(), localizedDescription));
            }

            return kvPairList;
}

Semelhante à anterior, permite obter uma lista de KeyValuePairs aceites como DataSource dos controlos.

Este foi, numa fase inicial, o tipo de estrutura que mais me preocupava, no sentido que os atributos poderiam complicar a globalização, e obrigar a um refactoring interno profundo. Felizmente, um simples workaround e refactoring evitou grandes mexidas. Também, porque nem os nomes dos métodos (nomeadamente o GetDDLList() ) nem os tipos de dados foram alterados, a camada superior continua a funcionar sem ter sido afectado.

Write a Comment

Comment

 

This blog is kept spam free by WP-SpamFree.