<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Miguel Alho - Multimédia</title>
	<atom:link href="http://www.miguelalho.com/?feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://www.miguelalho.com</link>
	<description>Just another WordPress weblog</description>
	<lastBuildDate>Thu, 26 Apr 2012 18:15:40 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Robert Myers &#8211; Evertything I&#8217;ve Learned, I Learded From Failure</title>
		<link>http://www.miguelalho.com/?p=1174</link>
		<comments>http://www.miguelalho.com/?p=1174#comments</comments>
		<pubDate>Sat, 09 Apr 2011 11:06:15 +0000</pubDate>
		<dc:creator>Miguel Alho</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://miguelalho.com/?p=1174</guid>
		<description><![CDATA[Um excelente talk no Info-Q a não perder! http://www.infoq.com/presentations/I-Learned-from-Failure]]></description>
			<content:encoded><![CDATA[<p>Um excelente talk no Info-Q a não perder!</p>
<p><a href="http://www.infoq.com/presentations/I-Learned-from-Failure">http://www.infoq.com/presentations/I-Learned-from-Failure</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.miguelalho.com/?feed=rss2&#038;p=1174</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Conferência SerFreelancer</title>
		<link>http://www.miguelalho.com/?p=1171</link>
		<comments>http://www.miguelalho.com/?p=1171#comments</comments>
		<pubDate>Sat, 05 Mar 2011 19:24:01 +0000</pubDate>
		<dc:creator>Miguel Alho</dc:creator>
				<category><![CDATA[Notícias]]></category>
		<category><![CDATA[Aveiro]]></category>
		<category><![CDATA[conferencia]]></category>
		<category><![CDATA[freelancing]]></category>

		<guid isPermaLink="false">http://miguelalho.com/?p=1171</guid>
		<description><![CDATA[Dia 2 de Abril vou ter o prazer de participar como orador na &#8220;Conferência &#8211; Ser Freelancer em Portugal&#8221;, no IPJ de Aveiro. vai ser certamente um dia muito interessante em que vamos reunir freelancers de diversas áreas, especialmente quem está a começar, para falar dos vários aspectos inerentes ao trabalho como freelancer. Vou estar [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://freelancerportugal.com/wp-content/themes/jenzoo/images/logo.png" alt="" width="450px" /></p>
<p>Dia 2 de Abril vou ter o prazer de participar como orador na &#8220;Conferência &#8211; Ser Freelancer em Portugal&#8221;, no IPJ de Aveiro. vai ser certamente um dia muito interessante em que vamos reunir freelancers de diversas áreas, especialmente quem está a começar, para falar dos vários aspectos inerentes ao trabalho como freelancer.</p>
<p>Vou estar na conferência a representar área da programação web e desenvolvimento de software. Tem piada que vem numa altura em que preparo-me para dar o salto do freelancing para a estrutura de empresa. De qualquer forma, não deixarei de partilhar os diversos aspectos da minha experiência como tal. Há sempre muito para dizer, e muitas questões a responder. Até lá, tenho que recuperar a voz!</p>
<p>Espero ver muito pessoal por lá. Se houver tópicos que alguem gostaria que abordasse em especial, deixa um comment!</p>
<p>Mais info em <a href="http://freelancerportugal.com/">http://freelancerportugal.com/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.miguelalho.com/?feed=rss2&#038;p=1171</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>FATAL:  could not reattach to shared memory (key=&#8230;, addr=&#8230;): 487</title>
		<link>http://www.miguelalho.com/?p=1169</link>
		<comments>http://www.miguelalho.com/?p=1169#comments</comments>
		<pubDate>Tue, 25 Jan 2011 10:07:55 +0000</pubDate>
		<dc:creator>Miguel Alho</dc:creator>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[base de dados]]></category>
		<category><![CDATA[PostresSQL]]></category>
		<category><![CDATA[Windows 2008 server]]></category>

		<guid isPermaLink="false">http://miguelalho.com/?p=1169</guid>
		<description><![CDATA[FATAL: could not reattach to shared memory (key=276, addr=01F20000): 487 Este é um erro que aparenta aparecer em algumas instalações PostgreSQL em Windows, em especial depois de existir algum update do OS. Uma instalação minha teve este problema, com o erro a ser registado periodicamente nos logs do PostgreSQL. Infelizmente o erro é problemático para [...]]]></description>
			<content:encoded><![CDATA[<p><code>FATAL:  could not reattach to shared memory (key=276, addr=01F20000): 487</code></p>
<p>Este é um erro que aparenta aparecer em algumas instalações PostgreSQL em Windows, em especial depois de existir algum update do OS. Uma instalação minha teve este problema, com o erro a ser registado periodicamente nos logs do PostgreSQL. Infelizmente o erro é problemático para aplicações dependentes da base de dados, uma vez que ele interrompe conecções entre a aplicação e a base de dados. É causador de um tipico erro de .NET com mensagem pouco esclarecedora:</p>
<p><code>[SocketException (0x2746): An existing connection was forcibly closed by the remote host]<br />
   System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size) +232</code></p>
<p>Poderia ser de uma coneção externa, mas o pilha de chamadas tinha claramente a biblioteca NpgSQL &#8211; o <em>provider </em>PostgreSQL para .NET na lista. Não era uma conecção entre cliente e servidor, mas sim entre a aplicação e o serviço de base de dados (que é efectuado sobre TCP, mesmo estando na mesma máquina).</p>
<p>A solução é a actualização da instalação do serviço de base de dados &#8211; este caso passei do 8.4.0 para o 8.4.6. (a correcção deve ter sido introduzido no 8.4.1). Esta versão terá o <em>patch </em>para a correcção deste erro. A actualização é simples: para o serviço e correr o instalador mantendo os dados e configurações intactas (poderá ser necessário recolocar o serviço a arrancar pelo sistema utilizador LOCAL SYSTEM, se necessário).</p>
<p>Info sobre o erro ou patch:<br />
<a href="http://blog.hagander.net/index.php?url=archives/149-Help-us-test-a-patch-for-the-Win32-shared-memory-issue.html#feedback" target="_blank">http://blog.hagander.net/index.php?url=archives/149-Help-us-test-a-patch-for-the-Win32-shared-memory-issue.html#feedback</a><br />
<a href="http://www.postgresql.org/docs/8.4/static/release-8-4-1.html" target="_blank">http://www.postgresql.org/docs/8.4/static/release-8-4-1.html</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.miguelalho.com/?feed=rss2&#038;p=1169</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Recuperação de arranque em Windows Server 2008</title>
		<link>http://www.miguelalho.com/?p=1166</link>
		<comments>http://www.miguelalho.com/?p=1166#comments</comments>
		<pubDate>Mon, 03 Jan 2011 10:32:05 +0000</pubDate>
		<dc:creator>Miguel Alho</dc:creator>
				<category><![CDATA[Windows]]></category>
		<category><![CDATA[boot]]></category>
		<category><![CDATA[bootrec]]></category>
		<category><![CDATA[diskpart]]></category>
		<category><![CDATA[recuperação]]></category>
		<category><![CDATA[Windows Server 2008]]></category>

		<guid isPermaLink="false">http://miguelalho.com/?p=1166</guid>
		<description><![CDATA[Pois. Este é um post que representa o meu dia de ano novo. A passagem foi excelente. O tarde após a passagem foi bastante assustador. Aproveitei a &#8220;paragem&#8221; para fazer uma limpeza ao meu servidor e introduzir uns discos novos. E assim se fez o caos! Passo a explicar&#8230; O meu servidor é relativamente simples [...]]]></description>
			<content:encoded><![CDATA[<p>Pois. Este é um post que representa o meu dia de ano novo. A passagem foi excelente. O tarde após a passagem foi bastante assustador. Aproveitei a &#8220;paragem&#8221; para fazer uma limpeza ao meu servidor e introduzir uns discos novos. E assim se fez o caos! Passo a explicar&#8230;</p>
<p>O meu servidor é relativamente simples e construído às necessidades. É um setup simples com dois discos de ITB em RAID 1 para sistema e repositórios de ficheiros importantes, e mais 3 discos de 640GB para &#8220;tralha&#8221;. Decidi fazer um upgrade, e acrescentar algum espaço &#8211; Ia juntar um disco de 640GB que estava na prateleira parada com uma das existentes num RAID 1 dedicado a backups, e acrescentar um disco de 1TB para mais tralha. Assim, ficaria com um total de 7 discos, representando cerca de 3TB de espaço, estando metade montado em RAID1. Uma unidade separada e dedicada a backups parece-me uma boa aposta, e quero implementar uma forma mais automatizada de os fazer, quer do servidor, da minha máquina e a dos meus colaboradores.</p>
<p>Portanto abri e limpei algum (pouco, felizmente) pó do servidor, e reorganizei a barra de discos que agora ficou completamente cheia. Introduzi o disco antigo que esteve parado (e que ainda tinha uma instalação de OS anterior do servidor, como backup), arranquei a máquina, criei um novo RAID e&#8230; O servidor entrou no OS antigo em vez da instalação actual. Arrancou pelo segundo disco em vez do primeiro. E assim o caos começou.</p>
<p>Obviamente não era isso que eu queria. Queria o arranque pelo mesmo OS que deveria estar intacto no DISCO 0. Infelizmente esta troca alterou o esquema de letras de unidades e o sector de arranque do disco principal., Não compreendi porque, infelizmente, mas tornou-se impossível arrancar pela partição de sistema do disco original, mesmo sendo a única unidade fisicamente ligada. Algo&#8230; assustador!</p>
<p>Para corrigir tive que arranjar algum método de corrigir, que não envolvê-se reinstalar. Felizmente encontrei alguns comandos que se podem executar para reconstruir o o sector e informação de arranque que passo a explicar. Há &#8220;ordem&#8221;, de certa forma, nos passos a executar q passo a descrever. Este foi o meu caso e raciocínio, mas obviamente pode mudar caso a caso.</p>
<p>O primeiro passo é introduzir e arrancar o servidor com o disco de instalação do Windows Server 2008. Ele tem ferramentas de recuperação, que podem ser executadas a partir da linha de comandos. Depois de arrancar, deve escolher a opção de reparação. Surge uma janela de escolha de unidade de sistema a recuperar. Infelizmente no meu caso, pelo menos inicialmente, não reconhecia nenhuma partição com uma instalação de sistema. De qualquer forma, escolhendo &#8220;NEXT&#8221;  permite chegar às opções seguintes e nas opções de ferramentas, escolhi a linha de comandos para abrir uma consola. Foi interessante verificar que, mesmo n conseguindo arrancar pelo OS, na consola, o windows ainda reconhecia a estrutura de partições e unidades presente no disco. Escolhendo a unidade C do sistema (>c:) executei o seguinte comando:</p>
<p><code>bootrec.exe /fixmbr</code></p>
<p>O comando repara o Master boot record. Em muitas situações este comando ou a combinação com o seguinte: </p>
<p><code>bootrec.exe /fixboot</code></p>
<p>Deveria ser suficiente para reparar o arranque. Infelizmente no meu caso não o foi, pois o  /fixboot respondeu com a mensagem de erro &#8220;Element Not Found&#8221;. Este erro significa, essencialmente, que a partição de sistema não está &#8220;activa&#8221;, e portanto deve ser activado, para o recuperador o encontrar. para tal, uso o DISKPART:</p>
<p><code><br />
>diskpart<br />
DISKPART>list disk<br />
...<br />
DISKPART>select disk 0<br />
...<br />
DISKPART>list partition<br />
...<br />
DISKPART>select partition 1<br />
...<br />
DISKPART>active<br />
DISKPART exit<br />
</code></p>
<p>Esta sequência demonstra alguns comandos do diskpart. os &#8220;list&#8221; listam os discos ou partições disponíveis no disco seleccionado. O &#8220;select&#8221; permite seleccionar e disco e partição desejada. &#8220;Active&#8221; activa a selecção. Depois de executar estes comandos, e com a partição activa, o bootrec.exe /fixboot executa correctamente e pode ser executado ainda o comando</p>
<p><code>bootrec.exe /rebuildbcd</code></p>
<p>Se tudo correr bem, deverá ser suficiente. Infelizmente, no meu caso, não o foi. Isto porque, no arranque, sugiu nova mensagem de erro, mas desta vez a indicar que o BOOTMGR estava em falta : &#8220;BOOTMGR IS MISSING&#8221;. Mais uma volta&#8230; desta vez foi suficiente executar o reparador de arranque no CD de instalação. Infelizmente, e ao contrario do Vista, esta opção não aparece na lista de ferramentas. Para aceder é necessário entrar na consola de comandos e executar:</p>
<p><code>X:\sources\recovery\StartRep.exe</code></p>
<p>Este foi o toque final necessário para ficar tudo OK. Pude finalmente concluir e ligar os restantes discos sem problemas dentro do servidor.</p>
<p>Refs:<br />
<a href="http://www.lancelhoff.com/how-to-fix-vista-mbr-repair-broken-vista/">http://www.lancelhoff.com/how-to-fix-vista-mbr-repair-broken-vista/</a><br />
<a href="http://www.pcstats.com/articleview.cfm?articleid=2264&#038;page=8">http://www.pcstats.com/articleview.cfm?articleid=2264&#038;page=8</a><br />
<a href="http://www.networksteve.com/forum/topic.php/VPN_created_using_modem_-_unable_to_modify/?TopicId=9937&#038;Posts=2">http://www.networksteve.com/forum/topic.php/VPN_created_using_modem_-_unable_to_modify/?TopicId=9937&#038;Posts=2</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.miguelalho.com/?feed=rss2&#038;p=1166</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Yaksha &#8211; Sociedade Violenta feat. Governo Sombra</title>
		<link>http://www.miguelalho.com/?p=1164</link>
		<comments>http://www.miguelalho.com/?p=1164#comments</comments>
		<pubDate>Mon, 11 Oct 2010 08:33:38 +0000</pubDate>
		<dc:creator>Miguel Alho</dc:creator>
				<category><![CDATA[Música]]></category>
		<category><![CDATA[Pessoal]]></category>
		<category><![CDATA[Gov Som]]></category>
		<category><![CDATA[Governo Sombra]]></category>
		<category><![CDATA[vídeos]]></category>
		<category><![CDATA[Yaksha]]></category>

		<guid isPermaLink="false">http://miguelalho.com/?p=1164</guid>
		<description><![CDATA[Foi lançado ontem à noite um novo vídeo do pessoal da Gov Som. Sociedade Violenta de Yaksha, com a participação de Governo Sombra foi filmado pela equipa de &#8220;La Casa Nostra&#8221;, e como habitual, participei com com assistência e iluminação. Acho que os planos com o bebé, em termos de luz, correram especialmente bem. Enjoy! [...]]]></description>
			<content:encoded><![CDATA[<p><object width="450" height="278"><param name="movie" value="http://www.youtube.com/v/HIeAltuwOx4?fs=1&amp;hl=pt_PT"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/HIeAltuwOx4?fs=1&amp;hl=pt_PT" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="450" height="278"></embed></object></p>
<p>Foi lançado ontem à noite um novo vídeo do pessoal da Gov Som. <strong>Sociedade Violenta</strong> de Yaksha, com a participação de Governo Sombra foi filmado pela equipa de &#8220;La Casa Nostra&#8221;, e como habitual, participei com com assistência e iluminação.  Acho que os planos com o bebé, em termos de luz, correram especialmente bem. Enjoy!</p>
<p>A faixa também está disponível para download em <a href="http://www.yousendit.com/download/WTNLRGx3T00zMW14dnc9PQ">http://www.yousendit.com/download/WTNLRGx3T00zMW14dnc9PQ</a>. Dia 23 sai o álbum, que, pelo que ouvi, está fortíssimo!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.miguelalho.com/?feed=rss2&#038;p=1164</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PostgreSQL 9.0 chegou</title>
		<link>http://www.miguelalho.com/?p=1161</link>
		<comments>http://www.miguelalho.com/?p=1161#comments</comments>
		<pubDate>Mon, 20 Sep 2010 13:25:05 +0000</pubDate>
		<dc:creator>Miguel Alho</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[base de dados]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://miguelalho.com/?p=1161</guid>
		<description><![CDATA[Foi oficialmente anunciado o lançamento da nova versão do PostgreSQL &#8211; a SGBD open source. Tenho usado a 8.3/8.4 muito nos últimos projectos e estou muito satisfeito com o desempenho, capacidade e funcionalidade da mesma. Esta nova versão anuncia algumas coisas novas e interessantes, como suporte para 64bits em Windows, Stored Procedures, triggers ao nível [...]]]></description>
			<content:encoded><![CDATA[<p>Foi oficialmente anunciado o lançamento da nova versão do PostgreSQL &#8211; a SGBD open source. Tenho usado a 8.3/8.4 muito nos últimos projectos e estou muito satisfeito com o desempenho, capacidade e funcionalidade da mesma. Esta nova versão anuncia algumas coisas novas e interessantes, como suporte para 64bits em Windows, Stored Procedures, triggers ao nível da coluna e condicional, e muito mais. Alista de novas features pode ser consultado no Wiki do PostgreSQL:</p>
<p><a href="http://wiki.postgresql.org/wiki/What's_new_in_PostgreSQL_9.0" target="_blank">http://wiki.postgresql.org/wiki/What&#8217;s_new_in_PostgreSQL_9.0</a></p>
<p>Mais:<br />
<a href="http://www.postgresql.org/about/press/presskit90" target="_blank">http://www.postgresql.org/about/press/presskit90</a><br />
<a href="http://www.postgresql.org/docs/9.0/static/release-9-0" target="_blank">http://www.postgresql.org/docs/9.0/static/release-9-0</a></p>
<p>Download:<br />
<a href="http://www.postgresql.org/download/" target="_blank">http://www.postgresql.org/download/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.miguelalho.com/?feed=rss2&#038;p=1161</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Vulnerabilidade de Segurança em Aplicações ASP.NET</title>
		<link>http://www.miguelalho.com/?p=1159</link>
		<comments>http://www.miguelalho.com/?p=1159#comments</comments>
		<pubDate>Sat, 18 Sep 2010 15:16:10 +0000</pubDate>
		<dc:creator>Miguel Alho</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[segurança]]></category>

		<guid isPermaLink="false">http://miguelalho.com/?p=1159</guid>
		<description><![CDATA[Foi anunciado ontem pela Microsoft uma vulnerabilidade no ASP.Net, associado a códigos de erro enviados pelo servidor para o cliente, e que poderiam servir para o utilizador aceder a ficheiros como o web.config. No blog do Scott Guthrie, ele refere como funciona a vulnerabilidade e como prevenir. Essencialmente, é necessário reeditar os ficheiros web.config, e [...]]]></description>
			<content:encoded><![CDATA[<p>Foi <a href="http://http://www.microsoft.com/technet/security/advisory/2416728.mspx" target="_blank">anunciado ontem pela Microsoft uma vulnerabilidade no ASP.Net</a>, associado a códigos de erro enviados pelo servidor para o cliente, e que poderiam servir para o utilizador aceder a ficheiros como o web.config.</p>
<p>No blog do Scott Guthrie, ele refere como funciona a vulnerabilidade e como prevenir. Essencialmente, é necessário reeditar os ficheiros web.config, e configurar de modo a ter os &#8220;custom errors&#8221; activos. Ele recomenda ainda ter apenas uma página de erro, e injectar algum código no Page_load dessa página:</p>
<pre lang="asp">
<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="System.Security.Cryptography" %>
<%@ Import Namespace="System.Threading" %>

<script runat="server">
   void Page_Load() {
      byte[] delay = new byte[1];
      RandomNumberGenerator prng = new RNGCryptoServiceProvider();

      prng.GetBytes(delay);
      Thread.Sleep((int)delay[0]);

      IDisposable disposable = prng as IDisposable;
      if (disposable != null) { disposable.Dispose(); }
    }
</script>

<html>
<head runat="server">

</head>
<body>
<div>
        An error occurred while processing your request.
    </div>

</body>
</html>
</pre>
<p>O detalhe completo em: <a href="http://weblogs.asp.net/scottgu/archive/2010/09/18/important-asp-net-security-vulnerability.aspx" target="_blank">http://weblogs.asp.net/scottgu/archive/2010/09/18/important-asp-net-security-vulnerability.aspx</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.miguelalho.com/?feed=rss2&#038;p=1159</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Internacionalização de ficheiros de JavaScript</title>
		<link>http://www.miguelalho.com/?p=1151</link>
		<comments>http://www.miguelalho.com/?p=1151#comments</comments>
		<pubDate>Sun, 29 Aug 2010 17:43:05 +0000</pubDate>
		<dc:creator>Miguel Alho</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Informática]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[i18n]]></category>
		<category><![CDATA[l10n]]></category>
		<category><![CDATA[T4]]></category>

		<guid isPermaLink="false">http://miguelalho.com/?p=1151</guid>
		<description><![CDATA[Os últimos posts no meu blog têm sido acerca do tema da internacionalização de aplicações, nomeadamente: ASP.Net – Método alternativo de internacionalização (i18n) Localizar Enumerações Adicionar novas línguas ao Windows (edições Home) O post de hoje contínua o sobre o método alternativo com ASP.NET, recorrendo a ao GetText do GNU. Como vimos, a aplicação do [...]]]></description>
			<content:encoded><![CDATA[<p>Os últimos posts no meu blog têm sido acerca do tema da internacionalização de aplicações, nomeadamente:</p>
<ul>
<li><a href="http://miguelalho.com/?p=1146" rel="bookmark" title="Permanent Link to ASP.Net – Método alternativo de internacionalização (i18n)">ASP.Net – Método alternativo de internacionalização (i18n)</a></li>
<li><a href="http://miguelalho.com/?p=1100" rel="bookmark" title="Permanent Link to Localizar Enumerações">Localizar Enumerações</a></li>
<li><a href="http://miguelalho.com/?p=1097" rel="bookmark" title="Permanent Link to Adicionar novas línguas ao Windows (edições Home)">Adicionar novas línguas ao Windows (edições Home)</a></li>
</ul>
<p>O post de hoje contínua o sobre o método alternativo com ASP.NET, recorrendo a ao <a href="http://www.gnu.org/software/gettext/manual/gettext.htm" target="_blank">GetText </a>do GNU. Como vimos, a aplicação do método _() para substituir texto localizável tornava-se um recurso simples e eficiente na internacionalização de ASP.NET. Em especial, é um método adoptado pela restantes linguagens e evita o esforço extra necessário para introduzir o texto em ficheiros .resx.</p>
<p>Procurei uma solução semelhante para Javascript, uma vez que a aplicação que estou a internacionalizar recorre muito a funcionalidade em javascript. Em especial, há mensagens de erro específicos e mensagens de confirmação contextualizadas. Especifico a ASP.Net, não encontrei nada de útil. Até achei muito estranho não detectar nada de muito útil nesse campo.</p>
<p>De qualquer forma, há alguns métodos genéricos a aplicar, incluíndo plugins em jQuery (<a href="http://plugins.jquery.com/project/gettext" target="_blank">http://plugins.jquery.com/project/gettext</a>), mas gostei da descrição dada em <a href="http://24ways.org/2007/javascript-internationalisation" target="_blank">24ways.org</a>. Simplesmente introduzi a função para _()  no meu ficheiro core de javascript (é uma pequena biblioteca e funções e abstracções comuns a todas as páginas das minhas aplicações, e incluído em todas as páginas):</p>
<pre lang="javascript">
function _(s) {
	if (typeof(i18n)!='undefined' &#038;&#038; i18n[s]) {
		return i18n[s];
	}
	return s;
}
</pre>
<p>Com isto é esperado existir uma variável <em>i18n </em>definido com a lista de traduções em pares chave-valor em JSON, do tipo:</p>
<pre lang="javascript">
var i18n = {
"" : "",
"Hello" : "Olá",
"Goodbye" : "Adeus",
//(...)
}
</pre>
<p>O método em 24ways.oprg refere ainda funções para formatar strings que podem ser uteis. Eu no entanto já tinha uma implementação de string.format para javascript (<a href="http://www.geekdaily.net/2007/06/21/cs-stringformat-for-javascript/" targte="_blank">http://www.geekdaily.net/2007/06/21/cs-stringformat-for-javascript/</a>).</p>
<p>Portanto, com a função no core, e a variável i18n adicionada, nos diversos ficheiros de javascript, onde fosse usado uma string, este deveria ser englobado por _():</p>
<pre lang="javascript">
if (confirm(_('Confirma a alteração de estado nos processos seleccionados?') )) {
//...
}
</pre>
<p>Assim, quando executado, e porque o método _() está sempre presente, o texto será substituído pelo existente no dicionário ou se não encontrado, o próprio texto usado como argumento da função.</p>
<p>Resta apenas dois passos &#8211; gerar os ficheiros .po, que deve seguir um esquema semelhante ao mencionado no artigo de ASP.Net, usando instruções póst-build e o gettext, e depois transformar os registos dos ficheiros .po em json. O primeiro dos passos é relativamente directa, apenas necessitando de construir uma nova lista de ficheiros (apenas de .js) e adicionar as entradas envolvidas em _() no .pot. </p>
<p>O segundo passo é mais &#8220;dificil&#8221;. Não há nada para o fazer directamentem senão um script em perl (<a href="http://jsgettext.berlios.de/doc/html/po2json.html" target="_blank">http://jsgettext.berlios.de/doc/html/po2json.html</a>), e como não me servia, decidi construir um script T4 simples, para usar no processo. </p>
<p>O script assume alguns pressupostos, pelo que, se utilizar, deve de os ter em conta e alterar consoante o necessário. O primeiro é que os ficheiros resultantes, quer os .po, quer os .js vão estar cada um numa pasta dedicada à língua específica como por exemplo:</p>
<p>/Scripts/locale/xx/messages.po<br />
/Scripts/locale/xx/i18n.js</p>
<p>Também, considero que o ficheiro .po será gerado e unido com recurso a msgmerge, e portanto apenas devo transformar os ficheiros .po em ficheiros .js</p>
<p>Assim, o po2json.tt, colocado na pasta (/Scripts/locale/) pode ser executado após o build. Ele procurará o ficheiro .po de cada língua no array, e transformarará em um ficheiro i18n.js. Eu decidi definir as linguas manualmente, mas com pequenas alterações, podem ser detectadas automáticamente.</p>
<p>O T4 é o seguinte:</p>
<pre lang="csharp">
<#@ Template Language="C#v3.5" Hostspecific="True" #>
<#@ Output Extension=".js" #>
<#@ Import Namespace="System.IO" #>
<#@ Import Namespace="System.Collections.Generic" #>
<#@ Import Namespace="System.Text.RegularExpressions" #>
<#@ Include File="MultiOutput.t4" #>
var i18n = {
<#
    string localeFolderPath = @"D:\Codigo\ProjectoWeb\Scripts\locale\";
    string[] suportedLang = {"pt"};
    string pofileName = "messages.po";
    string msgid = "msgid";
    string msgstr = "msgstr";
    string txtREGEX = "\"(.*)\"";

    foreach (string lang in suportedLang)
    {
        string[] lines = File.ReadAllLines(localeFolderPath + "\\" + lang + "\\"  + pofileName);
        List<KeyValuePair<string, string>> entries = new List<KeyValuePair<string, string>>();
        int currentLine = 0;

        while (currentLine < lines.Length)
        {
            string line = lines[currentLine];
            if (line.StartsWith(msgid))
            {
                string keyString = "";
                keyString += Regex.Match(line, txtREGEX).Groups[1].Value;

                //iterate while still a msgid
                line = lines[++currentLine];
                while(line.StartsWith("\""))
                {
                    keyString += Regex.Match(line, txtREGEX).Groups[1].Value;
                    line = lines[++currentLine];
                }

                if (line.StartsWith(msgstr))
                {
                    string valueString = "";
                    valueString += Regex.Match(line, txtREGEX).Groups[1].Value;

                    if (++currentLine < lines.Length)
                    {
                        line = lines[currentLine];
                        while (line.StartsWith("\""))
                        {
                            valueString += Regex.Match(line, txtREGEX).Groups[1].Value;
                            line = lines[++currentLine];
                        }
                    }
                    //add pair to entries list
                    entries.Add(new KeyValuePair<string, string>(keyString, valueString));
                }
            }

            currentLine++;
        }

        //build outoput
        for( int i = 0; i< entries.Count; i++)
        {
            KeyValuePair<string, string> entry = entries[i];

#>
"<#=  entry.Key #>" : "<#= entry.Value #>"<#=    i<entries.Count-1 ? "," : "" #>
<#
        }
    SaveOutput(localeFolderPath + "\\" + lang + "\\"  + "i18n.js"); 

    }
#>
};
</pre>
<p>Procura cada &#8220;msgid&#8221; e processa as línguas seguintes convenientemente. Se houver erros, o processo será abandonado. O tt recorre ainda a um MultiOutput.t4, para gerar mais que um ficheiro a partir do .tt, convenientemente localizado. Não necessita de estar incluído no projecto (para não obrigar a incluir as dependências de TextTempleting).</p>
<p>Para incluir o ficheiro na lista de scripts a puxar, basta regista-lo no ScriptManager, no Page_Load da MasterPage :</p>
<pre lang="csharp">
ScriptManager.RegisterClientScriptInclude(this, typeof(string), "i18n", Page.ResolveUrl("~/Scripts/locale/" + lang + "/i18n.js"));
</pre>
<p>onde a variável lang é uma string com as letras da língua, para identificar a pasta onde será armazenado. Eu obtenho a a língua para a aplicação (transversal), mas outra possibilidade seria definir a língua pelas preferências do utilizador. Também, o caminho poderia ser proveniente de uma chave de configuração no web.config, se necessário.</p>
<p>Creio que está é uma solução simples, que pode, se necessário ser melhorado com mais funções (especialmente de formatações e plural / singular) como descrito no artigo do 24ways.org. Para já, esta é me suficiente.</p>
<p><a href='http://miguelalho.com/wp-content/uploads/2010/08/po2json.zip'>Download po2json t4</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.miguelalho.com/?feed=rss2&#038;p=1151</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ASP.Net &#8211; Método alternativo de internacionalização (i18n)</title>
		<link>http://www.miguelalho.com/?p=1146</link>
		<comments>http://www.miguelalho.com/?p=1146#comments</comments>
		<pubDate>Wed, 25 Aug 2010 00:41:32 +0000</pubDate>
		<dc:creator>Miguel Alho</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[i18n]]></category>
		<category><![CDATA[internactionalização]]></category>
		<category><![CDATA[l10n]]></category>

		<guid isPermaLink="false">http://miguelalho.com/?p=1146</guid>
		<description><![CDATA[A internacionalização de aplicações (internationalization &#8211; i18n) para posterior localização (localization &#8211; l10n) é um processo multi-passo, que envolve a detecção de texto que deve ser apresentado em múltiplas línguas, bem como a preparação da base de código para que o texto possa ser correctamente traduzido. Cada tecnologia tem a sua própria forma de implementar [...]]]></description>
			<content:encoded><![CDATA[<p>A internacionalização de aplicações (internationalization &#8211; i18n) para posterior localização (localization &#8211; l10n) é um processo multi-passo, que envolve a detecção de texto que deve ser apresentado em múltiplas línguas, bem como a preparação da base de código para que o texto possa ser correctamente traduzido.</p>
<p>Cada tecnologia tem a sua própria forma de implementar mecanismos que auxiliam a tradução. .Net recorre a recursos compilados na forma de ficheiros .resx . Os ficheiros .resx são de uma forma geral pares chave-valor, estruturados em XML, que são compilados para dlls carregados no inicio da aplicação.</p>
<p>Apesar de uma solução interessante, é extremamente tediante usa-los em ASP.Net. Com as bibliotecas de uma API, ok. O suporte a nível de código C# (ou VB ) torna o uso relativamente simples. Já com ASP.Net, não é possível dizer o mesmo. As razões estão bem descritas em &#8220;<a href="http://www.expatsoftware.com/articles/2010/03/why-internationalization-is-hopelessly.html" target="_blank">Why Internationalization is hopelessly Broken in ASP.Net</a>&#8220;, mas resumindo, tudo tem de ser controlos ASP.Net com o runat=&#8221;server&#8221;, não há suporte simplificado a texto estático (escrito directamente na página), a referência de chaves das strings obriga a inserir meta:resoursekey=&#8221;" em todas os controlos, e os ficheiros .resx não são facilmente legíveis por pessoal não técnico.</p>
<p>A alternativa? Seguir o método do &#8220;resto do mundo&#8221; e usar ficheiros de strings em formatos simplificados, nomeadamente os ficheiros .po. Quem já explorou um projecto open-source ou projectos aplicacionais com add-ins linguísticos, muito provavelmente já se cruzaram com este formato de ficheiros. São ficheiros de texto simples, com texto assoiado a uma chave (msgid) e texto associado à tradução (msgstr). Por exemplo:</p>
<pre lang="text">msgid "Pesquisar Candidatos"
msgstr "Search Candidates"</pre>
<p>De notar neste exemplo que a chave está em PT e a tradução em EN. Não é recomendado esta abordagem, uma vez que as línguas latinas tem acentos que não são óptimos para chaves.</p>
<p>Continuando, o uso de ficheiros .PO, noutras frameworks, é suportado por por uma função gettext(), onde é passo a chave (geralmente o próprio texto, na língua base) e retorna a versão traduzida, conforme as preferências de utilizador. Melhor, há geralmente uma forma simplificada da função escrito _(). Assim, cada string a analisar chama a função gettext(), obtendo a versão traduzida do texto. Bem mais simples que os .resx e meta:resourcekey do .NET.</p>
<h2>Usando Fairlylocal</h2>
<p>O <a href="http://www.fairtutor.com/fairlylocal/" target="_blank">FailyLocal </a> é um projecto opensource iniciado por Jason Kester, e que implementa a funcionalidade do gettext e ficheiros PO para ASP.Net. Como descrito no link do projecto, para pedir a tradução, na página basta utilizar:</p>
<pre lang="asp">
<%= _("texto a traduzir") %>
</pre>
<p>ou no codebehind:</p>
<pre lang="csharp">controlo.InnerHtml = _("texto a traduzir");</pre>
<p>Tão simples e eficaz. Em termos de biblioteca, basta adicionar os ficheiros ao projecto da aplicação (têm de ser um projecto de aplicação web, em vez de um projecto de website, uma vez que o segundo não <a href="http://www.fairtutor.com/fairlylocal/fairlylocal-details#basepage" target="_blank">permite evento post-build, que o primeiro permite, e que constrói dinamicamente os ficheiros PO</a>).</p>
<p>Em termos de execução, os ficheiros PO são carregados no arranque da aplicação, para um dicionário estático em memória, onde depois são realizadas as consultas do gettext(). Neste caso, todas as paginas devem herdar de InternationalPage ou InternationalMaster (ou a página base da aplicação derivar destas) em vez de Page directamente, para que _() fique disponível.</p>
<p>Para as minhas necessidades, efectuei pequenas alterações. Primeiro, para facilitar o uso em mutlipos projectos, adicionai os ficheiros ao projecto da minha framework, para que tenha sempre disponível. Segundo, porque necessito de aceder aos métodos, quer para a página instanciada, quer quando estou a usar WebMethods, tornei a propriedade LanguageCode e o método _() static. Finalmente, porque necessito de definir a língua ao nível da aplicação e não para cada utilizador, modifiquei o getter de LanguageCode para obter o código de lingua directamente da thread, em vez da sessão do utilizador:</p>
<pre lang="csharp">public static string LanguageCode
{
   get
   {
       if (_languageCode == null)
       {
           _languageCode = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.ToLower();
	}
	return _languageCode;
    }
    set
    {
	_languageCode = value;
    }
}</pre>
<p>Incluíndo os scripts post-build, os ficheiros .PO são actualizados após cada build, podendo ser traduzidos de seguida, ou directamente ou recorrendo a programas como o <a href="http://www.poedit.net/index.php" target="_blank">Poedit</a>. Ferramentas como o <a href="http://pepipopum.dixo.net/" target="_blank">Pepipopum </a>permitem traduzir automaticamente (via Google translate API) o ficheiro PO. As chaves devem estar em inglês para funcionar correctamente.</p>
<p>Finalmente, convém referir que o sistema tem fallback automático para o texto da própria chave &#8211; se o testo não estiver traduzido no ficheiro .po, o texto a usar é o próprio argumento do _(), o que ajuda muito se tiver a internacionalizar uma aplicação já existente.</p>
<h2>Uma macro util</h2>
<p>Ainda assim, com esta simplificação, internacionalizar uma aplicação existente não deixa de ser um trabalho tediante. Localizar o texto a traduzir e envolver no método _() é &#8220;chato&#8221;.</p>
<p>Para ajudar, criei uma macro:</p>
<pre lang="vb">
Public Module GetTextSurrond

    '' surround text with <%= _(" and ") $>
    ''
    Sub ASPXSurround()
        Dim textSelection As EnvDTE.TextSelection

        textSelection = DTE.ActiveDocument.Selection()
        textSelection.Text = "<%= _(""" + textSelection.Text + """) " ''%>"
    End Sub

End Module
</pre>
<p>Associando esta Macro a um atalho de teclas, por exemplo Ctrl+G, Ctrl+T, ajudará a usar a macro, bastando seleccionar o texto e executar o comando (nos ficheiros .aspx).</p>
<h2>Conclusão</h2>
<p>Pela curta experiência que tenho com esta alternativa de tradução, não deixa de me parecer N vezes mais vantajoso que o uso de ficheiros .resx no ASP.Net.  Os ficheiros .PO são apenas strings, portanto para recursos binários outra estratégia deve ser adoptada.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.miguelalho.com/?feed=rss2&#038;p=1146</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Localizar Enumerações</title>
		<link>http://www.miguelalho.com/?p=1100</link>
		<comments>http://www.miguelalho.com/?p=1100#comments</comments>
		<pubDate>Mon, 05 Jul 2010 23:46:19 +0000</pubDate>
		<dc:creator>Miguel Alho</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[enum]]></category>
		<category><![CDATA[enumeração]]></category>
		<category><![CDATA[globalization]]></category>
		<category><![CDATA[locale]]></category>
		<category><![CDATA[ToString()]]></category>

		<guid isPermaLink="false">http://miguelalho.com/?p=1100</guid>
		<description><![CDATA[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 [...]]]></description>
			<content:encoded><![CDATA[<p>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.</p>
<p>Parte das enumerações usadas são automaticamente escritas com uma estrutura que era óptima.. até agora. A aplicação é <em>N-layer</em>, dividido em camadas lógicas. Por enquanto, e porque ainda não foi necessário mais, mantém-se <em>monotier</em>. As camadas lógicas neste caso são UI, BLL (<em>Business Lógic Layer</em>) e DAL (<em>Data Access Layer</em>), mais os repositórios de dados. Comum a todas as camadas há duas bibliotecas &#8211; 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 (<em>Business Objects</em>) que inclui as classes conceptuais e DTOs (<em>Data Transfer Objects</em>) do domínio. Os objectos nesta biblioteca são &#8220;dummy objects&#8221;, 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 <a href="http://imar.spaanjaars.com/416/building-layered-web-applications-with-microsoft-aspnet-20-part-1" target="_blank">Building Layered Web Applications with Microsoft ASP.NET 2.0</a>.</p>
<p>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:</p>
<pre lang="csharp">
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
    }
}
</pre>
<p>A descrição escrita como atributo, recorrendo ao <em>System.ComponentModel.DescriptionAttribute</em> 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 <em>ToString()</em>. No exemplo em cima, O caso do &#8220;Outro&#8221; demonstra a possível diferença. Para obter os valores, na minha <em>framework</em> tenho um método numa <em>helper class</em> que permite obter o valor da descrição, bem como uma <em>extension method</em> para ajudar:</p>
<pre lang="csharp">
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);
    }
}
</pre>
<p>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).</p>
<p>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 <em>T4</em>. Assim, a minha enumeração fica simplificada:</p>
<pre lang="csharp">
[Serializable]
public enum EnumTipoContacto
{
	Telefone = 1,
        Telemovel = 2,
        Email = 3,
        Fax = 4,
        Outro = 5,
}
</pre>
<p>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:</p>
<pre lang="xml">
(...)
<data name="Namespace.BO.EnumTipoContacto.Telefone" xml:space="preserve">
    <value>Telefone</value>
</data>
 <data name="Namespace.BO.EnumTipoContacto.Telemovel" xml:space="preserve">
    <value>Telemóvel</value>
</data>
 <data name="Namespace.BO.EnumTipoContacto.Email" xml:space="preserve">
    <value>Email</value>
</data>
 <data name="Namespace.BO.EnumTipoContacto.Fax" xml:space="preserve">
    <value>Fax</value>
</data>
 <data name="Namespace.BO.EnumTipoContacto.Outro" xml:space="preserve">
    <value>Outro Tipo / Desconhecido</value>
</data>
(...)
</pre>
<p>Para obter o valor da chave, a operação é ligeiramente diferente:</p>
<pre lang="csharp">
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;
}
</pre>
<p>Neste exemplo, <em>tipo.GetType()</em> devolve <em>Namespace.BO.EnumTipoContacto</em>, permitindo construir a chave. é necessário ter em atenção que linha</p>
<pre lang="csharp">
ResourceManager resources = Namespace.BO.Properties.Enum.ResourceManager;
</pre>
<p>obriga-me a que a assemblagem gerada seja o <em>Namespace.BO.dll</em> e o ficheiro de recursos é o <em>Enum.resx</em> ou <em>Enum.xx.resx</em> onde <em>xx</em> representa o código cultural (&#8220;es&#8221;, por exemplo). Novamente, porque o meu código é em boa parte autogerado, tenho controlo sobre esse formato.</p>
<p>Outro método muito útil, para usar como <em>DataSource</em> para preencher <em>DropDownLists </em>e controlos do género é a seguinte:</p>
<pre lang="csharp">
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;
}
</pre>
<p>Semelhante à anterior, permite obter uma lista de <em>KeyValuePairs </em>aceites como <em>DataSource </em>dos controlos.</p>
<p>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 <em>workaround </em>e <em>refactoring </em>evitou grandes mexidas. Também, porque nem os nomes dos métodos (nomeadamente o <em>GetDDLList</em>() ) nem os tipos de dados foram alterados, a camada superior continua a funcionar sem ter sido afectado.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.miguelalho.com/?feed=rss2&#038;p=1100</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

