IIS, WCF e Partial Trust

Há algum tempo eu falei sobre a possibilidade de invocar serviços WCF em aplicações parcialmente confiáveis. Mas ainda há um segundo cenário, que é a exposição de serviços através de aplicações ASP.NET Web Site/WCF Service. Para criar um serviço WCF, você não precisa necessariamente criar um tipo de projeto exclusivo como o WCF Service. Um projeto do tipo ASP.NET Web Site pode, tranquilamente, servir como host de um serviço WCF, e para isso, basta adicionar um arquivo *.svc e configurá-lo corretamente no Web.config.

Independentemente de qual das alternativas utilize, você poderá se deparar com uma restrição de segurança, e dependendo das funcionalidades (mais precisamente do binding e seus elementos) que utiliza, o serviço não rodará. Isso muitas vezes acontece quando você faz o deployment para um servidor, em que a configuração padrão do .NET Framework foi alterada (visando uma maior segurança). Uma das regras mais importantes que se deve ter ao configurar um servidor Web (IIS), é não permitir que as aplicações que rodem ali executem em “Full Trust”. Para isso, se altera o arquivo Web.config (que está em nível de servidor), definindo o atributo level do elemento trust para “Medium” ou qualquer nível abaixo disso.

Neste ambiente, você poderá utilizar os bindings BasicHttpBinding, WSHttpBinding ou o WebHttpBinding, desde que eles estejam com a segurança desabilitada ou com a proteção em nível de transporte. O binding WSDualHttpBinding também não pode ser utilizado neste cenário, já que algumas tarefas que ele desempenha exige um nível de segurança mais elevado. Finalmente, para tentar resolver este problema, podemos fazer uso das técnicas mostradas pelo Juval Lowy neste artigo, disponibilizando alguns helpers para facilitar a criação de hosts em ambientes parcialmente confiáveis.

Serviços Declarativos

Uma nova funcionalidade que estará presente no WCF 4.0, é a capacidade de criar serviços de forma declarativa. Quando trabalhamos com WCF, tudo o que devemos fazer é criar uma Interface que representará o contrato, implementamos ela em uma classe que representará o serviço e, finalmente, criamos o código necessário para servir como hosting para o serviço.

A criação do hosting conseguimos fazer totalmente de forma declarativa, enquanto as outras duas, somente podemos utilizar o modelo imperativo (via C# ou VB.NET) para criá-los. A proposta da Microsoft é permitir que a criação de um serviço WCF seja feita totalmente (definição e implementação) de forma declarativa, ou seja, utilizando o XAML. Ainda há o que chamamos de “projeção de contratos”, que permitirá separar a definição da representação (mensagens). Com isso, vamos ter apenas uma única definição, mas podendo ser representada de diferentes formas (SOAP, REST, etc.). Para mais detalhes sobre serviços declarativos, você pode consultar a documentação oficial desta funcionalidade.

Utilizando Generics em Serviços

Uma das grandes desilusões ao utilizar o WCF, é não conseguir utilizar tipos de dados genéricos nos serviços. Generics são tipos de dados exclusivos do .NET Framework, e utilizá-los viola os princípios da orientação a serviços, já que podem existir linguagens que não suportam essa funcionalidade. É importante dizer que isso não é uma limitação do WCF, mas sim do WSDL, que é o documento utilizado para expor os metadados para os consumidores.

O Visual Studio .NET/compilador não vão proibí-lo de utilizá-los nos contratos. O problema será na exposição das informações (via WSDL) para o cliente e, consequentemente, o proxy gerado possuirá tipos “estranhos”. Por exemplo, se uma operação do contrato retornar ou receber um tipo genérico como Teste<int>, ao gerar o proxy, ele irá criar um tipo (classe) chamado TesteOfInt, substituindo os parâmetros genéricos do mesmo, pelo tipo especificado na operação. Isso pode piorar um pouco se o parâmetro genérico informado for uma classe, como por exemplo Teste<OutraClasse>, resultando no seguinte tipo do lado do cliente: TesteOfOutraClasseKdjwuy4p, ou seja, acrescentando uma informação randômica para evitar eventuais conflitos com outros tipos no mesmo contrato.

Felizmente você pode sobrescrever esse comportamento, definindo a propriedade Name da atributo DataContractAttribute. Nela você pode especificar o formato do nome que deseja expor no WSDL. Se notarmos no primeiro exemplo abaixo, o parâmetro {0} será substituído pelo tipo genérico especificado. Caso a classe Teste suportasse mais do que um parâmetro genérico, então você pode também especificá-los para compor o nome, assim como mostrado no segundo exemplo.

[DataContract(Name = “TesteCom{0}”)]
public class Teste<T> { }

[DataContract(Name = “TesteComTipo{0}EComChave{1}”)]
public class Teste<T, D> { }

Um outro ponto importante é com relação as coleções. Elas também são características da plataforma, e não devem ser expostas além do serviço. Ao utilizar coleções que implementam direta ou indiretamente as Interfaces IEnumerable<T>, ICollection<T> ou IList<T>, o WCF automaticamente irá convertê-las em um array do tipo especificado no parâmetro genérico (<T>), facilitando a interoperabilidade com outras plataformas.

Ainda falando em coleções, ao criar uma coleção customizada, você poderá fazer uso do atributo CollectionDataContractAttribute, permitindo customizar o nome do tipo da coleção (através da propriedade Name) a ser colocado no WSDL e, consequentemente, criado no cliente (trabalhando da mesma forma que a propriedade Name do atributo DataContractAttribute). Ao utilizar este atributo, ainda temos a garantia de que o WCF verificará a existência do método Add durante a carga do serviço, e não existindo, uma exceção do tipo InvalidDataContractException será lançada. Este método se faz necessário para remontar a coleção durante a deserialização da mensagem.

Como dito acima, as coleções são características da plataforma, e não devem propagar além dos limites do serviço. Mas em algumas situações, isso pode ser interessante. Quando você está compartilhando tipos entre o cliente e o serviço (exige .NET/WCF presente dos dois lados), você pode optar por criar o proxy respeitando e preservando essas coleções. Para que isso seja possível, basta você utilizar a opções /reference/collectionType do utilitário svcutil.exe. A primeira opção determina o Assembly (reference) onde está a coleção, enquanto a segunda opção determina qual é a coleção. É importante dizer que este recurso também está disponível quando a criação do proxy é feita pela IDE do Visual Studio .NET.