Localization em ASP.NET

No início desta semana, um colega de trabalho me perguntou como funcionam os conceitos Globalization e Localization do ASP.NET e, como não é comum encontrar pessoas que os utilizam na prática, surgiu a idéia de falar um pouquinho sobre eles aqui no blog e explicar do que se trata e como agir para colocá-los em prática.

Hoje vou falar um pouco sobre o Localization!

O que é Localization (ou Localização)?

Localization é a palavra escolhida para definir um conceito fantástico que se resume em tornar sua página Web independente do idioma do visitante, ou seja, caso um brasileiro visite seu site, o verá em português, caso o visitante seja um norte-americano, visualizará em inglês.

Antigamente, para realizar tal tarefa o desenvolvedor poderia optar por, basicamente, duas alternativas:

· Criar uma versão da página para cada idioma;

· Desenvolver um mecanismo que verificasse o idioma do usuário e buscasse em um arquivo ou banco de dados os textos corretos a serem exibidos, exibindo-os na página.

A primeira opção, embora seja a mais simples de executar, duplicaria o tamanho total do seu Website, além da necessidade de se criar uma página principal onde o usuário escolheria, no início da navegação, com qual idioma ele gostaria de prosseguir.

Notada por muitos como a solução mais elegante, a segunda opção passou então a ser o foco dos arquitetos de sistemas voltados para a Web, uma vez que, dentre várias vantagens, seria possível modificar ou incluir um idioma sem a necessidade de acessar o código-fonte da aplicação, ou seja, dando segurança para o projeto e independência para as equipes de desenvolvimento e tradução.

Como a necessidade de “localização†é comum a quase todos os projetos de software (seja para Web ou não), a Microsoft apresentou este mecanismo já implementado no .NET Framework, sendo possível tornar sua aplicação independente de idioma com apenas alguns procedimentos e cliques (sem digitar uma linha sequer de código).

Como utilizar Localization na minha página ASP.NET?

Hoje em dia ficou fácil adicionar suporte a um determinado idioma na sua aplicação Web desenvolvida em ASP.NET. Veja como fazer, passo a passo:

· No Visual Studio 2008, clique em File, New, Project;

· Em Project Types, escolha sua linguagem de desenvolvimento favorita e em Templates escolha ASP.NET Web Application.

  • Adicione alguns controles à página Default.aspx (ver imagem):

· Através do Solution Explorer, clique com o botão direito em Default.aspx e clique em View Designer no menu flutuante;

· Arraste do ToolBox um controle Label para a página, definindo seu texto para “Seja bem vindo!†e seu ID para “cLabelWelcomeâ€;

· Arraste do ToolBox um controle Button para a página, definindo seu texto para “Clique aqui para entrar†e seu ID para “cButtonEnterâ€.

  • Crie o arquivo de Resource com a língua padrão (ver imagem):

· Clique no menu Tools, Generate Local Resource.

  • Crie o arquivo de Resource com a língua alternativa:

· Através do Solution Explorer, clique com o botão direito no arquivo Default.aspx.resx que está localizado dentro da pasta App_LocalResources criada pelo Visual Studio 2008 e clique em Copy;

· Clique com o botão direito sobre a pasta App_LocalResources e clique em Paste;

· Renomeie o arquivo Copy of Default.aspx.resx para Default.aspx.en-us.resx e clique duas vezes sobre ele para abri-lo;

· Substitua o texto “Clique aqui para entrar†por “Click here to enterâ€, assim como o texto “Seja bem vindo!†por “Welcome!â€;

· No menu do Visual Studio 2008, clique em File, Save App_LocalResources\Default.aspx.en-us.resx.

Pronto! Sua aplicação já oferece suporte para o idioma português (idioma padrão) e inglês… Para adicionar novos idiomas, repita a etapa 4, mas ao renomear o arquivo, substitua “en-us†pelo código da língua desejada, como “en-ca†para inglês canadense ou “es-mx†para espanhol mexicano.

No momento em que sua página for acessada, o ASP.NET verificará o idioma marcado no navegador do usuário e encontrará o arquivo de Resource correto, que será aplicado à página para exibir os textos corretamente. Caso o arquivo correto não seja encontrado (usuário esteja utilizando um idioma que não possui um arquivo de Resource para ele), a página irá utilizar o arquivo padrão (aquele que não tem o código do idioma).

Para testar sua aplicação, execute o projeto e verifique o idioma em que foram exibidos os textos do site. Em seguida, no Internet Explorer, vá em Tools (Ferramentas), Internet Options (Opções de Internet) e clique no botão Languages (Idiomas). Com o botão Add (Adicionar), escolha a opção English (United States) [en-US] e clique em Ok. Com ajuda do botão Move Up (Acima), coloque a nova opção como a primeira da lista e dê Ok nas duas janelas abertas.

localization04

Clique em Refresh (Atualizar) ou pressione F5 no navegador para ver a página aplicando os textos conforme a nova linguagem definida no browser

Bom, é isso pessoal. No próximo post vamos falar um pouco sobre Globalization. Até lá!

Performance em Silverlight – Dica Rápida #3

Utilize o método Double.ToString(CultureInfo.InvariantCulture) ao invés de simplesmente Double.ToString().

O método Double.ToString(IFormatProvider) que recebe CultureInfo.InvariantCulture como argumento é otimizado para obter melhor performance.

Em geral, quando não se deseja exibir um dado formatado ao usuário ou obter dados sensíveis à culturas diferentes (como, por exemplo, durante a comparação de duas strings), a melhor prática, segundo a Microsoft, é a utilização de Double.ToString(CultureInfo.InvariantCulture).

Se, porém, sua aplicação irá exibir números ao usuário e é necessária a adequação de tais números à cultura local, você deverá utilizar Double.ToString(IFormatProvider) recebendo CultureInfo.CurrentCulture como argumento ou simplesmente Double.ToString(), pois as duas formas adéquam o resultado à cultura corrente.

Fonte: MS Help (Performance Tips)

Performance em Silverlight – Dica Rápida #2

Se você deseja simplesmente exibir ou ocultar um objeto na tela e não precisa deixá-lo parcialmente transparente, utilize a propriedade Visibility ao invés de Opacity.

A propriedade Opacity acarreta em alto custo computacional, pois o objeto continuará sendo tecnicamente “renderizado†e testado pelo Hit Test.

Segundo a Microsoft, modificar a propriedade Visibility para Collapsed é a prática mais recomendável para evitar estes custos caso seu interesse não seja tornar o objeto translúcido, mas escondê-lo na tela.

Fonte: MS Help (Performance Tips)

Performance em Silverlight – Dica Rápida #1

Desenvolver animações que envolvem aumentar ou diminuir o tamanho de um texto pode consumir muitos recursos do sistema. Isso acontece porque o Silverlight arredonda os cantos de todo objeto texto.

Se você animar o tamanho de um texto (utilizando o objeto Transform ou a propriedade FontSize), o Silverlight irá arrendondar os cantos do texto à cada frame, o que possui um alto custo computacional e pode resultar em erros durante a exibição dos frames.

Se a sua aplicação requer a alteração dinâmica de uma grande quantidade de texto, a Microsoft sugere a utilização de uma imagem vetorial que represente o texto a ser modificado.

Fonte: MS Help (Performance Tips)

Compartilhar pasta na rede com C#

O Microsoft .NET Framework, embora seja bem amplo, ainda não possui APIs que permitem criar compartilhamentos de pastas e arquivos no Windows. Todavia, é possível criar um novo compartilhamento (e várias outras tarefas específicas do Sistema Operacional) através de chamadas a objetos WMI (Windows Management Instrumentation).

Para este artigo, preparei uma função que compartilha um diretório específico em uma rede Microsoft. Embora seja possível definir várias propriedades avançadas do compartilhamento, decidi omitir as configurações de segurança (direitos de acesso) para tornar o código mais simples e compreensível a qualquer um, portanto, se você tiver interesse em criar compartilhamentos com direitos de acesso que não sejam somente-leitura, deixe uma mensagem aqui no blog ou leia a documentação da classe WMI que vamos utilizar (Win32_Share).

Então vamos ao que interessa. Primeiramente, adicione ao seu projeto uma referência à namespace System.Management, clicando com o botão direito em “References†e, em seguida, em “Add Reference…â€. Selecione “System.Management†na janela que abrir e clique no botão “OKâ€.

Primeiramente é aconselhável que criemos dois Enums com alguns valores pré-definidos, para que não fiquemos interagindo com nossa função através de números hexadecimais.


        /// <summary>
        /// Types of resource that will be shared.
        /// </summary>
        protected enum ShareType : uint
        {
            DiskDrive = 0x0,
            PrintQueue = 0x1,
            Device = 0x2,
            IPC = 0x3,
            DiskDriveAdmin = 0x80000000,
            PrintQueueAdmin = 0x80000001,
            DeviceAdmin = 0x80000002,
            IPCAdmin = 0x80000003
        }

        /// <summary>
        /// Result types of creation of the share.
        /// </summary>
        protected enum ShareResult : uint
        {
            Success = 0,
            AccessDenied = 2,
            UnknownFailure = 8,
            InvalidName = 9,
            InvalidLevel = 10,
            InvalidParameter = 21,
            DuplicateShare = 22,
            RedirectedPath = 23,
            UnknownDeviceOrDirectory = 24,
            NetNameNotFound = 25
        }

Notem que as duas Enums acima não são Enums de números inteiros simples, mas de inteiros “sem sinalâ€, ou seja, “uintâ€. Isso porque os valores pré-definidos são grandes demais para serem armazenados em variáveis do tipo inteiro (padrão das Enums) e serão sempre positivas, por isso fez-se necessário herdar, para cada Enum, o tipo “uintâ€.

E, finalmente, a nossa função que instancia a classe “Win32_Share†do WMI e faz a chamada ao método “Createâ€, para criar o compartilhamento do recurso:


        /// <!-- Thiago Marotta Couto -->
        /// <!-- February, 07 - 2009 -->
        /// <!-- http://isbyte.com -->
        /// <summary>
        /// Method that starts a sharing folder on the local machine.
        /// </summary>
        /// <param name="path">Local path of the Windows share (folder that will be shared).</param>
        /// <param name="shareName">Alias to a path set up as a share.</param>
        /// <param name="type">Type of resource that will be shared.</param>
        /// <param name="maximumAllowed">Maximum number of users allowed to concurrently use this resource. Optional (nulleable) parameter.</param>
        /// <param name="description">Optional comment to describe the resource being shared. Optional (nulleable) parameter.</param>
        /// <param name="password">Password (when the server is running with share-level security) for the shared resource. If the server is running with user-level security, this parameter is ignored. Optional (nulleable) parameter.</param>
        /// <returns>Result of the creation of the share.</returns>
        protected static ShareResult ShareFolder(string path, string shareName, ShareType type, uint? maximumAllowed, string description, string password)
        {
            System.Management.ManagementClass wmiWin32_Share;
            System.Management.ManagementBaseObject inputParameters;
            System.Management.ManagementBaseObject outputParameters;

            //Verifies if the received arguments are correct.
            if ((path == null) || (path == string.Empty))
                throw new ArgumentNullException("The path argument can not be null or empty.");

            if ((shareName == null) || (shareName == string.Empty))
                throw new ArgumentNullException("The shareName argument can not be null or empty.");            

            try
            {
                //Instantiate the "Win32_Share" WMI class.
                wmiWin32_Share = new System.Management.ManagementClass(@"Win32_Share");

                //Recover the parameters of "Create" method.
                inputParameters = wmiWin32_Share.GetMethodParameters(@"Create");

                //Defines mandatory parameters.
                inputParameters.SetPropertyValue(@"Path", path);
                inputParameters.SetPropertyValue(@"Name", shareName);
                inputParameters.SetPropertyValue(@"Type", Convert.ToUInt32(type));

                //Defines optional parameters if received.
                if (maximumAllowed != null) inputParameters.SetPropertyValue(@"MaximumAllowed", maximumAllowed);
                if (description != null) inputParameters.SetPropertyValue(@"Description", description);
                if (password != null) inputParameters.SetPropertyValue(@"Password", password);

                //Invokes the "Create" method.
                outputParameters = wmiWin32_Share.InvokeMethod(@"Create", inputParameters, null);

                //Returns the result of "Create" method.
                return (ShareResult)outputParameters.GetPropertyValue(@"ReturnValue");
            }
            catch
            {
                //An unknown error occurred.
                return ShareResult.UnknownFailure;
            }
        }

É importante notar que somente administradores do sistema ou usuários com credenciais Communication, Print ou Server têm permissão para executar o método “Create†da classe “Win32_Share†e, conseqüentemente, nossa função. Operadores de impressão somente poderão adicionar filas de impressão, assim como operadores de comunicação somente poderão adicionar filas de comunicação. Para testar o funcionamento destas permissões, execute a função recém criada com o Visual Studio sem elevação de permissão e, em seguida, em modo administrador.

Para executar a função, utilize a seguinte sintaxe:


            ShareResult result;

            //Try to share MyPictures user folder.
            result = ShareFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
                "MyPictures Folder", ShareType.DiskDrive, null, "My personal photos.", null);

            MessageBox.Show("The result of ShareFolder method: " + result.ToString());

As classes WMI permitem que você execute tarefas bem interessantes, além de recuperar informações e características do computador que talvez sejam possíveis somente através delas. Não deixe de conferir a documentação completa com todas as classes, propriedades e métodos aqui no site do MSDN.

Qualquer dúvida utilize o espaço para comentários do blog. Boa diversão ;)

Utilizando CACHE do ASP.NET com MS ACCESS, em C#

Desempenho de software é um assunto que sempre desperta o meu interesse, e foi graças a isso que acabei descobrindo um dos recursos mais fantásticos disponíveis no ASP.NET, o Cache.

Todo desenvolvedor de software sabe que uma das partes mais lentas de uma aplicação é o acesso ao disco rígido, isso porque ainda trabalhamos com um modelo de disco rígido baseado em cabeça mecânica de leitura de dados e outras características que não vem ao caso. O problema é que muitas operações costumeiras durante o desenvolvimento de um software estão diretamente ligadas ao acesso a disco, como leitura e gravação de arquivos, acesso a banco de dados, etc. E foi graças a esse tipo de “gargalo†da computação que um ser iluminado do passado pensou em um recurso que ficou conhecido como Cache. Três vivas para o inventor do Cache: IoI IoI IoI.

A palavra Cache, na computação, diz respeito a uma quantidade de memória utilizada para armazenar dados temporariamente, de modo a fornecê-los rapidamente quando solicitados pelo processador sem a necessidade de acessar sua origem (muitas vezes localizada no disco rígido, que é cerca de dez vezes mais lento que a memória principal).

O Cache do ASP.NET não é diferente, é um recurso que tem como função armazenar objetos na memória principal do computador. Adicionalmente, o Cache permite a adição de dependências ao objeto guardado, de forma que se a dependência sofrer alguma alteração, o objeto contido no Cache é automaticamente destruído para que não se torne inconsistente.

Com ele, podemos criar aplicações mais rápidas, principalmente se tais aplicações necessitam de muitos acessos a um banco de dados, por exemplo.

Para explicar como funciona tudo isso, vou mostrar como desenvolver uma aplicação que carrega um banco de dados MS Access na memória principal do computador, de modo que não seja necessário acessar o disco rígido para ler as informações contidas nele caso já tenha sido carregado para o Cache. Adicionalmente esta aplicação deve manter a consistência dos dados, ou seja, se o banco de dados for atualizado pela própria aplicação ou por terceiros, o sistema deverá reconhecer automaticamente e atualizar também o Cache.

Para esta aplicação utilizei um banco de dados bem simples, criado no MS Access 2007, que consiste em uma única tabela onde será gravado o primeiro e o último nome de algumas pessoas.

 accessusertable

Obs.: Nomeie o banco de dados como “database.accdb†e adicione-o à pasta “App_Data†do seu projeto, no Visual Studio, pois o código fonte foi desenvolvido partindo do princípio que o nosso banco está localizado lá dentro.

Primeiramente, vamos criar um método que acessa o banco de dados MS Access 2007 e retorna um DataSet já carregado com as informações que desejamos:


        private System.Data.DataSet GetData(string accessFileName, string query)
        {
            System.Text.StringBuilder connectionString;
            System.Data.DataSet dataSet;

            //Initializes System.Text.StringBuilder.
            connectionString = new System.Text.StringBuilder();

            //Creates connection string based on file name.
            connectionString.AppendFormat(@"Provider=Microsoft.ACE.OLEDB.12.0;");
            connectionString.AppendFormat(@"Data Source= {0};", accessFileName);

            using (System.Data.OleDb.OleDbConnection connection = new System.Data.OleDb.OleDbConnection(connectionString.ToString()))
            {
                //Creates System.Data.OleDb.OleDbCommand.
                System.Data.OleDb.OleDbCommand command = new System.Data.OleDb.OleDbCommand(query, connection);

                //Creates System.Data.OleDb.OleDbDataAdapter.
                System.Data.OleDb.OleDbDataAdapter adapter = new System.Data.OleDb.OleDbDataAdapter(command);

                //Initializes DataSet.
                dataSet = new System.Data.DataSet();

                //Fills DataSet.
                adapter.Fill(dataSet);
            }

            //Returns data.
            return dataSet;
        }

Agora vem a mágica: Um método que recebe e adiciona à memória principal o objeto que será armazenado em Cache, uma chave identificadora e o arquivo físico ao qual o objeto está ligado em uma relação de dependência:


        private void SetCache(string cacheKey, object item, string fileNameDependency)
        {
            System.Web.Caching.CacheDependency dependency;

            //Creates dependency based on received file name.
            dependency = new System.Web.Caching.CacheDependency(fileNameDependency);

            //Stores data in the ASP.NET Cache.
            Cache.Insert(cacheKey, item, dependency);
        }

E finalmente, vamos interligar tudo no manipulador do evento Page_Load da nossa aplicação Web:


        protected void Page_Load(object sender, EventArgs e)
        {
            const string cFileName = @"~/App_Data/database.accdb";
            const string cQuery = @"SELECT [ID], [UserName], [LastName] FROM [User]";
            const string cCacheKey = @"User";
            System.Data.DataSet dsUser;

            //Gets data from the ASP.NET Cache.
            dsUser = (System.Data.DataSet)Cache.Get(cCacheKey);

            //If data are not present...
            if (dsUser == null)
            {
                //Gets data from database.
                dsUser = this.GetData(Server.MapPath(cFileName), cQuery);

                //Saves data in the ASP.NET Cache.
                this.SetCache(cCacheKey, dsUser, Server.MapPath(cFileName));
            }

            //Shows data.
            GridView1.DataSource = dsUser;
            GridView1.DataBind();
        }

Reparem que para simplificar a aplicação, foram criadas três constantes, sendo a primeira o caminho do arquivo de banco de dados, a segunda a query que será executada e a terceira uma chave única para identificar o nosso objeto no Cache do ASP.NET.

Para visualizar o resultado na tela, também foi adicionado um GridView à página, com o ID “GridView1â€, onde serão exibidas as informações do banco.

Se preferir, clique aqui para baixar a solução completa (código fonte e banco de dados).

Para testar o funcionamento, insira um breakpoint no início do método Page_Load e rode a aplicação. Executando linha a linha, repare que inicialmente o nosso objeto ainda não existe no Cache e, por isso, retornará um DataSet nulo. Desta forma, a aplicação terá que acessar o banco de dados e trazer, pela primeira vez, as informações para o Cache.

Depois da primeira execução, dê um reload no navegador e o método Page_Load será executado novamente. Desta vez, o objeto estará presente no Cache e não será necessário acessar o banco de dados para exibir as informações na tela.

Depois da segunda execução, insira um novo registro no banco de dados (lembre-se de salvar e fechar a tabela aberta, caso faça a inserção diretamente pelo MS Access 2007). Dê um reload no navegador e note que ao procurar o nosso objeto no Cache ele não existe mais. Isso porque o ASP.NET detectou que o arquivo sofreu alteração e removeu da memória as informações armazenadas. Neste caso, a aplicação irá retornar ao banco de dados para recarregar os dados na memória.

Fantástico, não?!

Essa tecnologia pode ser aplicada a qualquer tipo de banco de dados baseado em arquivo, como Firebird, SQLite ou mesmo XML, arquivos MS Excel ou de texto. Existe também a possibilidade de utili