Tuples

Especulando o .NET Framework 4.0 no Reflector, eu vi que há uma estrutura de Tuples. Tuples é uma forma que temos para representar algum dado, ou um conjunto deles, sem necessariamente ter uma classe/estrutura por trás disso. Tudo na programação orientada à objetos e nas linguagens fortemente tipada, temos que criar fisicamente a classe e sua estrutura de propriedades para, em seguida, poder utilizá-la.

As Tuples tem um papel importante na programação dinâmica, e como sabemos que C# está cada vez mais ganhando alguns conceitos deste tipo, as Tuples trazem uma importante característica para o nosso código. Muitas vezes já nos deparamos com um cenário onde precisamos retornar mais do que um resultado. Neste caso, utilizamos o retorno tradicional do método em conjunto com alguns parâmetros de saída (out/ref). Apesar de conseguir contornar com a criação de tipos (classes) adicionais, as Tuples vão muito além, já que não há a necessidade da criação deste tipo.

Os tipos anônimos tem uma funcionalidade semelhante, mas tem um escopo limitado, podendo ser acessado somente dentro do bloco onde foi criado. As Tuples vão mais além, sendo possível definí-las como retorno de métodos, propagando para outros lugares.

As Tuples são imutáveis, com tamanho fixo e permite o acesso diretamente a cada um dos valores através de propriedades “dinamicamente criadas”. Assim como os tipos anônimos e variáveis declaradas com o “tipo” var, a tipificação será determinada através da atribuição do(s) valor(es) à Tuple. Notem que nos exemplos abaixo, não especificamos em nenhum momento os tipos que estamos trabalhando:

var tuple = Tuple.Create(1, “Israel Aece”); 
var id = tuple.Item1; 
var nome = tuple.Item2;

//ou algo assim:

var tuple = AlgumaFuncaoQueRetornaMaisDeUmValor();
var id = tuple.Item1; 
var nome = tuple.Item2;

Infelizmente, na versão atual do CTP do .NET 4.0, as classes que representam as Tuples estão definidas como internal. Essas classes estão sendo criadas para melhorar a interoperabilidade com o F#, mas espero que elas se tornem públicas até a versão final, para que possamos fazer uso em nossas aplicações.

Embutindo o PIA

Quando referenciamos um componente COM em um aplicativo .NET, o Visual Studio .NET nos auxilia na criação de um Assembly de Interoperabilidade, conhecido como Primary Interop Assembly, ou somente PIA. Na verdade, a IDE recorre à um utilitário chamado tlbimp.exe, que dado um determinado componente COM, gera uma DLL com toda estrutura (tipos) do componente COM, já “traduzindo” os tipos COM para tipos .NET, facilitando assim o consumo por uma aplicação .NET.

Uma das melhorias que o .NET 4.0 está trazendo é a possibilidade de embutirmos esse Assembly de interoperabilidade no Assembly da aplicação. Para isso, basta ir até as propriedades da referência COM e definir a propriedade Embed Interop Types como True. Isso evita ter um Assembly exclusivo para servir como “ponte” entre a aplicação .NET e o componente COM. Lembro-me de alguns poucos projetos que tivemos que utilizar esse recurso que, como se já não bastasse o overhead que é causado pelo RCW, a distribuição era terrível, já que tínhamos que registrar efetivamente o componente COM no Windows, e instalar o Assembly de interoperabilidade e a aplicação em si.

Além de facilitar a distribuição, esse recurso ainda se preocupa em inserir no Assembly da aplicação somente os tipos necessários que a mesma utiliza, diminuindo consideravelmente o tamanho final da mesma. Podemos notar isso através da imagem abaixo:

pia

Strong Name Bypass

Um dos passos necessários durante a carga do Assembly é análise da assinatura do Strong Name. Independentemente do Code Access Security, essa análise sempre é realizada e muitas aplicações pagam esse preço sem ao menos utilizá-lo. A partir do SP1 do .NET Framework 3.5, a Microsoft incluiu uma funcionalidade chamada de Strong Name Bypass, não mais fazendo essa verificação e, consequentemente, tendo um ganho de performance durante a sua inicialização.

Essa análise não será mais realizada para Assemblies que são carregados em um ambiente full-trusted. Obviamente que essa análise acontecerá quando, para computar a segurança, voce levar em consideração o Strong Name. Se, por algum motivo, voce quiser reabilitar a verificação, então pode recorrer ao elemento bypassTrustedAppStrongNames, definindo o atributo enabled para false, assim como é mostrado abaixo, utilizando o arquivo de configuração:

<configuration>
    <runtime>
        <bypassTrustedAppStrongNames enabled=”false”/>
    </runtime>
</configuration>

Melhorando a performance no ThreadPool

Como já sabemos, a classe ThreadPool disponibiliza um repositório de threads que podemos utilizar em nossas aplicações, delegando a ela uma tarefa para ser executada através de um delegate. Para isso, é muito comum utilizarmos o método QueueUserWorkItem para enfileirar uma nova tarefa. O exemplo abaixo ilustra a sua utilização em uma aplicação qualquer:

ThreadPool.QueueUserWorkItem(state =>
{
    Console.WriteLine(new StreamReader(@”C:TempArquivo.txt”).ReadToEnd());
});

Quando utilizamos o método QueueUserWorkItem, antes de enfileirar a tarefa a ser executada, ele captura todas as permissões que foram concedidas e, antes de executá-la, essas permissões são aplicadas a thread que está associada à tarefa. Com isso, se a tarefa que voce está tentando executar de forma assíncrona exigir alguma permissão e ela não foi concedida, uma exceção do tipo SecurityException será disparada. Podemos notar esse comportamento com o seguinte código:

new FileIOPermission(FileIOPermissionAccess.Read, @”C:TempArquivo.txt”).Deny();

ThreadPool.QueueUserWorkItem(state =>
{
    Console.WriteLine(new StreamReader(@”C:TempArquivo.txt”).ReadToEnd());
});

No código acima, a aplicação não possui permissão de acesso ao arquivo, e ao executar o método QueueUserWorkItem, a tarefa vinculada também não conseguirá executar a leitura do arquivo, disparando uma exceção. Por menor que seja, utilizar este método sempre tem o overhead para a cópia das permissões atuais e associá-las a thread responsável pela tarefa, para que ela execute no mesmo contexto de segurança.

É justamente neste ponto que entra em cena o método UnsafeQueueUserWorkItem, também da classe ThreadPool. Ao contrário do método QueueUserWorkItem, ele não propaga as permissões para a worker thread, diminuindo o trabalho a ser realizado quando voce enfileira uma nova tarefa ao ThreadPool. A utilização em relação ao que vimos acima muda ligeiramente:

new FileIOPermission(FileIOPermissionAccess.Read, @”C:TempArquivo.txt”).Deny();

ThreadPool.UnsafeQueueUserWorkItem(state =>
{
    Console.WriteLine(new StreamReader(@”C:TempArquivo.txt”).ReadToEnd());
}, null);

Como as permissões não são propagadas e avaliadas com o método UnsafeQueueUserWorkItem, mesmo que a aplicação não tenha as devidas permissões, a execução da tarefa ocorre sem maiores problemas.

É importante dizer aqui que a performance é melhorada mas, em contrapartida, a segurança fica vulnerável. Utilizar este tipo de código pode abrir portas para algum código malicioso, que pode utilizá-lo para elevar os privilégios de uma aplicação e, consequentemente, executar tarefas que atualmente ela não tem permissão. Opte sempre por utilizar esta técnica em uma tarefa em que voce não dependerá de nenhum recurso do sistema operacional (sistema de arquivos, registry, SQL Server, etc.), utilizando em conjunto com tarefas que dependam exclusivamente do processador, como é o caso de cálculos complexos, geração de algum conteúdo, etc., melhorando consideravelmente a performance na execução, sem abrir potenciais problemas de segurança.

CSE – Corrupted State Exceptions

A classe Exception é a base para todas as exceções do .NET Framework. Tanto exceções do próprio .NET quanto aquelas que criamos, herdam direta ou indiretamente dela, independentemente de sua severidade.

Tendo isso em mente, nunca foi tão perigoso utilizar a instrução catch(Exception). Mesmo em “aplicações finais”, que são aquelas que devem ter um handler para tratar as possíveis exceções, utilizar esta técnica pode ser muito perigoso. Existem algumas exceções que são consideradas “exceções de sistema”, como é o caso da AccessViolationException, e como também derivam da classe Exception, serão capturadas e “tratadas” pelo handler catch(Exception). Mas isso não quer dizer que voce pode/deve continuar utilizando a aplicação, já que essas exceções comprometem o estado do respectivo processo.

Para resolver este tipo de problema, a Microsoft está disponibilizando junto ao .NET 4.0 o conceito de CSE (Corrupted State Exceptions). Com este novo recurso, a CLR não entregará exceções que podem danificar o processo para o handler catch(Exception), a menos que voce faça isso explicitamente (apesar de não ser uma boa prática), decorando o método que pode causar a falha com o atributo HandleProcessCorruptedStateExceptionsAttribute. Para maiores informações sobre essa nova funcionalidade, consulte este artigo.

Múltiplas condições no JOIN com LINQ

Em minhas deambulações com o LINQ, me surgiu uma dúvida de como proceder para efetuar um join com duas condições na cláusula on. Inicialmente tentei da forma mais fácil, ou seja, utilizando o operador &&, mas sem muito sucesso. Com um pequena busca, encontrei um post no MSDN em que o próprio Anders Hejlsberg explica como proceder. Simplesmente basta criarmos um tipo anônimo e combinarmos os campos que desejamos comparar, assim como é mostrado no exemplo abaixo:

from sala in cadastroDeSalas
join curso in Cursos on
    new
    {
        cadastroDeSalas.Campo1,
        cadastroDeSalas.Campo2
    }
    equals 
    new 
    {
        curso.Campo1,
        curso.Campo2 
    }

A necessidade do casting

Muitas vezes vejo em fóruns alguns trechos de códigos em que o pessoal faz o casting de um controle em outro para acessar uma determinada propriedade. O que quero chamar a atenção aqui é que nem sempre esses castings são necessários e, por menor que seja, efetuá-los sempre tem o seu custo.

Todo controle em ASP.NET herda direta ou indiretamente da classe Control. Essa classe fornece grande parte das propriedades e métodos que todo server-control deve ter. Imagine que dentro de um controle DataBound, como o ListView, voce possui um controle ASP.NET qualquer, e queira definir a sua propriedade Visible como False. Para exemplificar, notem que abaixo estou procurando pelo controle “Calendar1” através do método FindControl. Com o retorno deste método (que é um objeto derivado da classe Control) eu faço o casting para o controle Calendar e, consequentemente, defino a propriedade Visible como False.

protected void ListView1_ItemDataBound(object sender, ListViewItemEventArgs e)
{
    if (e.Item.ItemType == ListViewItemType.DataItem)
    {
        ((Calendar)e.Item.FindControl(“Calendar1”)).Visible = false;
    }
}

Como dito anteriormente, a propriedade Visible está definida na classe Control, e com isso não há necessidade de efetuar o casting para acessá-la. Voce só precisa efetuar o casting caso queira acessar uma propriedade exclusiva do controle. Para finalizar, abaixo temos o mesmo código sem o casting, e tendo o resultado conforme o esperado:

protected void ListView1_ItemDataBound(object sender, ListViewItemEventArgs e)
{
    if (e.Item.ItemType == ListViewItemType.DataItem)
    {
        e.Item.FindControl(“Calendar1”).Visible = false;
    }
}

CAS != Windows Security

Uma das funcionalidades mais interessantes do .NET Framework ao meu ver é o CAS – Code Access Security. Toda aplicação que roda sobre a plataforma .NET pode interagir de forma direta ou indireta com o CAS e, com isso demandar os privilégios necessários para que ela possa ser executada.

Quando a aplicação é inicializada, a mesma é carregada para dentro de um processo, que é chamado também de host. Esse host extrai e examina a evidência do Assembly, podendo diferentes informações serem coletadas de acordo com a origem do mesmo. A evidência consiste no strong-name, zona e publicador do Assembly. Depois de capturada, essa evidência é passada para o sistema de segurança do .NET Framework (CAS) para que o mesmo avalie e conceda as devidas permissões para o Assembly.
 
A partir deste momento, baseando-se na evidência extraída do Assembly, o runtime security policy começará a avaliar a “árvore” de code groups. Se a condição for atendida, dizemos que o Assembly é membro do code group e, além disso, o conjunto de permissões (permission set) vinculadas a essa condição será concedido ao Assembly. Caso contrário, possíveis code groups que estiverem abaixo da condição não atendida, não serão avaliados e, obviamente, não serão concedidos. Depois deste processo, a união dos conjuntos de permissões é computada e esse processo é repetido para cada nível das políticas de segurança (policy levels) que são: User, Machine e Enterprise. Finalmente, depois de todos os níveis avaliados, o Assembly receberá a interseção de todos os grupos de permissões entre os níveis, e o Assembly receberá o que chamamos de Final Permission Grant (FG).

Essas permissões são definidas a partir do utilitário caspol.exe para aplicações Windows ou, para aplicações ASP.NET, através dos arquivos *.config que estão localizados no diretório do .NET Framework. Independentemente do tipo de aplicação, mesmo que ela ganhe permissão para acesso ao sistema de arquivos, isso não quer dizer que a aplicação irá executar uma operação (de escrita ou leitura) com sucesso.

A questão é que o CAS garante as permissões que o código tem sobre o sistema operacional. Quando executamos uma aplicação ela está sempre associada à uma identidade dentro do sistema operacional. Em aplicações Windows o padrão é rodar com as credenciais do usuário corrente que está logado no sistema operacional; já em aplicações ASP.NET, o que prevalece é o usuário do IIS, mais precisamente, a identidade definida na criação do worker process. Com esse comportamento, de nada adianta a aplicação receber o FileIOPermission se o usuário que está associado à execução naquele momento não possuir os devidos privilégios de acesso dentro do Windows.

requirePermission

As seções connectionStrings e appSettings são as únicas que podemos acessar diretamente no código sem nenhum tipo de problema, mesmo quando a aplicação está sendo executada em um ambiente parcialmente confiável. Por parcialmente confiável em uma uma aplicação ASP.NET, entenda como o trust level esteja definido como “Medium” ou algo abaixo disso:

<trust level=”Medium”/>

Neste caso, qualquer outra seção (authentication, smtp, globalization, etc.) que tentarmos acessar em um neste ambiente, uma exceção do tipo SecurityException será lançada informando que não possuímos a permissão necessária que, neste caso, é ConfigurationPermission. Note que só o fato de acessar a seção já provoca o disparo da exceção:

AuthenticationSection section =
    (AuthenticationSection)WebConfigurationManager.GetSection(“system.web/authentication”);
Response.Write(section.Mode.ToString());

Quem determina essa restrição é o atributo requirePermission que é configurado no registro da seção dentro do arquivo de configuração. Por padrão, ele é definido como True e, como dito acima, somente as seções connectionStrings e appSettings são definidas como False, como se pode notar no arquivo machine.config. Somente altere a configuração padrão caso voce realmente saiba o que está fazendo e, principalmente, conhecendo as implicações que isso pode causar. Além disso, é importante dizer que voce também pode determinar essa restrição quando voce cria uma seção de configuração customizada.

.NET 3.5 SP1 GDR (Fixes)

Está disponível a partir deste link uma atualização para o .NET Framework 3.5 SP1. Esta atualização também faz algumas poucas mudanças no WCF e, entre elas, o envio correto do código de status do protocolo HTTP quando a autenticação via HTTP é inválida.

Como já foi mencionado neste kb, quando utilizamos um autenticador customizado no WCF, disparamos a exceção SecurityTokenValidationException para informar ao runtime que o usuário é inválido. Quando o runtime identifica essa exceção, ele traduz a mesma para o código 403 (Forbidden) do HTTP. A mudança que é realizada com esta atualização é que ao invés de retornar 403, passará a retornar o código 401 (Unauthorized), que acaba sendo mais coerente com a situação.