Melhorias no WCF com SP1

O Service Pack 1 do .NET Framework 3.5 e o Serviço Pack 1 do Visual Studio 2008 trouxeram algumas melhorias interessantes no WCF:

  • UriTemplate: agora podemos incluir um valor padrão para algum parametro que será invocado via REST. Algo como: [WebGet(UriTemplate = “RecuperarUsuarios/{quantidade=5}”)].
  • Atributo DataContract/DataMember: a partir de agora não precisamos explicitamente definir quais são os objetos e propriedades que serão expostos para serviços quando estamos trabalhando com tipos complexos. Ele entende que tudo será exposto. Temos apenas que nos preocuparmos em marcar o que não queremos que seja serializado.
  • Partial Trust: possibilidade de fazer o uso do Event Log em ambiente parcialmente confiável.
  • Hosting Wizard: basicamente é a mesma funcionalidade do “Publish Web Site”, mas para um serviço WCF.
  • WCF Options: quando tem um projeto do tipo Wcf Service Library na solução, nas propriedades deste projeto temos uma aba chamada WCF Options. Dentro da mesma, há uma opção chamada: Start WCF Service Host when debugging another project in the same solution. Ao marcá-la, ao rodar qualquer projeto em modo de depuração, o WCF Test Client irá iniciar para tornar o serviço disponível para que outras aplicações na mesma solução o utilizem.

Arquivos de Configuração para serviços COM+

Há algum tempo, quando eu ainda fazia curso de COM+, em alguma das minhas noites em claro eu encontrei uma dica interessante (desculpe-me, mas não lembro da referencia) que talvez, utilizaria em algum lugar.

Hoje precisei fazer o uso de uma library em um dos componentes que está rodando dentro do COM+. Até então sem muitos problemas, até que vi que algumas configurações desta library precisavam ser realizadas no arquivo de configuração. Mas onde está o arquivo de configuração de componentes que estão hospedados no COM+? Se a aplicação do COM+ fosse do tipo Library (que é criada dentro do mesmo processo do criador), bastaria eu colocar as configurações no arquivo de configuração da aplicação cliente mas, no meu caso, a aplicação tratava-se de uma aplicação do tipo Server (que é criada em um processo a parte (dllhost.exe)).

Se formos até as propriedades da aplicação COM+, na aba Activation, veremos que há um campo chamado Application Root Directory. Podemos colocar ali o caminho até um diretório no disco exclusivo para esta aplicação e, dentro deste diretório, precisamos também criar 2 arquivos: um é o próprio arquivo tradicional de configuração (application.config); já o segundo, trata-se de um manifesto (application.manifest), que descreve as dependencias que a aplicação requer, mas que neste caso, pode ser simplesmente:

<?xml version=”1.0″ encoding=”UTF-8″ standalone=”yes”?>
<assembly xmlns=”urn:schemas-microsoft-com:asm.v1″ manifestVersion=”1.0″ />

Agora pode-se rodar sem maiores problemas que os dados serão capturados deste arquivo. Como o dllhost.exe está dentro do %Windir%System32, eu acredito que se seguirmos o padrão do .NET, que é criar um arquivo de configuração dentro do mesmo diretório da aplicação, deve funcionar sem problemas (dllhost.exe.config), mas o problema é que isso pode servir várias aplicações e poderemos ter confusões/complicações aqui.

Depurando Host com Client

Durante o desenvolvimento de serviços WCF ou qualquer outra coisa que envolva Host e Client, é comum utilizarmos o recurso de depuração de múltiplos projetos que o Visual Studio fornece. Assim, sempre quando um processo dá problemas (exceções), o outro continua rodando até que, explicitamente, voce o finalize.

Sara Ford dá aqui uma dica interessante para encerrar todos os processos, caso algum deles de problema durante a execução.

Postando para um serviço WCF

Uma das coisas mais interessantes que podemos fazer com os serviços RestFul é a possibilidade de postar um conteúdo de um formulário de uma página Web diretamente para um serviço. Isso possibilitará aplicações HTML, PHP, ASP, etc., enviar informações para um serviço WCF, sem a necessidade de conhecer e/ou criar o protocolo SOAP.

O código cliente muda ligeiramente, apenas definindo a URL do serviço como sendo o action do formulário:

<form action=”http://localhost:3344/srv/Send” method=”post”>
    Nome: <input type=”text” name=”nome” />
    E-mail: <input type=”text” name=”email” />
    <input type=”hidden” name=”campoOculto” value=”xpto” />

    <input type=”submit” value=”Enviar” />
</form>

Há alguns detalhes na implementação do serviço para permitir isso. Detalhes que foram abordados aqui.

WCF – RestFull

A versão 3.5 do Windows Communication Foundation introduziu uma nova forma de expor e consumir serviços. Esse novo modelo, também conhecido como Web Programming Model, permite o consumo destes serviços através dos mais variados clientes, como é o caso dos navegadores.

Como sabemos, o WCF implementa vários protocolos (padrões) WS-* sob o SOAP, como é o caso de transações, mensagens confiáveis, etc.. Desta forma, sempre é necessário a construção deste envelope SOAP para realizar a requisição e, também através de um envelope SOAP, o serviço nos envia a resposta do processo executado e isso tudo é abstraído pelo runtime do WCF ou qualquer outro framework.

Assim como os antigos Web Services (ASMX), essa nova funcionalidade permite ao WCF expor um serviço e ser acessado a partir do protocolo HTTP diretamente, sem a necessidade da criação de um envelope SOAP para isso. Este modelo tem algumas características, como o suporte as operações do serviço via métodos GET e POST do HTTP; as URIs identificam o endpoint e que operação deve invocar. Os possíveis parâmetros que o método exige são recuperados da QueryString ou do corpo da mensagem (isso depende do método utilizado). Além disso, este modelo ainda permite a serialização da requisição/resposta em XML ou JSON.

Para fazer o uso desta nova funcionalidade, além de precisa referenciar o Assembly System.ServiceModel.dll, é preciso também adicionar a referência para o Assembly System.ServiceModel.Web.dll que foi introduzido a partir da versão 3.5 do .NET Framework. Este novo Assembly trará uma série de tipos (classes, interfaces e atributos) que serão utilizados para a configuração e execução de um serviço a ser exposto desta forma. Inicialmente analisaremos os tipos relacionados à configuração do serviço e, em seguida, os tipos relacionados a execução do serviço.

O primeiro detalhe que precisamos nos atentar é com relação a dois atributos de configuração que devem ser aplicados a interface do contrato de serviço: WebGetAttribute e WebInvokeAttribute. Esse atributos devem ser utilizados em conjunto com o atributo OperationContractAttribute ao invés de substituí-lo.

Os serviços que são expostos no formato Web fazem o uso de verbos como o HTTP GET para recuperação de dados e, uso de verbos como o HTTP POST para efetuar a invocação de um determinado método, assim como já comentamos acima. Os atributos WebGetAttribute e WebInvokeAttribute permite ao desenvolvedor controlar (de forma individual) o formato da URI a ser exposta e como associar um determinado verbo com as operações que o serviço (contrato) fornece.

Quando decoramos um método com o atributo WebGetAttribute, possibilitamos que ele seja invocado via HTTP GET. Com esse atributo e mais algumas configurações que veremos mais adiante, é possível invocarmos esse método a partir de uma URI, sem a necessidade do envelope SOAP e, automaticamente, o runtime do WCF faz o mapeamento do método e possíveis parâmetros e, em seguida, encaminha a requisição para o respectivo método/serviço. Além disso, esse atributo possui várias propriedades que merecem destaque e, estão abordadas na tabela abaixo:

Propriedade Descrição
BodyStyle Essa propriedade controla o estilo do corpo da mensagem. Quando o serviço ou o cliente serializa os parâmetros e valores de retorno, esses valores podem ou não serem escritos dentro de elementos Xml adicionais. Ela recebe uma das opções fornecidas pelo enumerador WebMessageBodyStyle, que estão descritas logo abaixo:

  • Bare: Tanto a requisição quanto a resposta não são envolvidas por esses elementos adicionais. Caso essa propriedade não seja definida, o Bare é o valor padrão.
  • Wrapped: Tanto a requisição quanto a resposta são envolvidas por esses elementos adicionais.
  • WrappedRequest: Apenas a requisição será envolvida dentro desses elementos adicionais.
  • WrappedResponse: Apenas a resposta será envolvida dentro desses elementos adicionais.
RequestFormat Essa propriedade controla o formato (serialização) das requisições para a operação. Ela recebe uma das opções fornecidas pelo enumerador WebMessageFormat, que estão descritas logo abaixo:

  • Json: Define e utiliza o formato JSON (JavaScript Object Notation). Utilizado em cenários AJAX.
  • Xml: Define e utiliza o formato Xml. Caso essa propriedade não seja definida, o Xml é o valor padrão.
ResponseFormat Essa propriedade controla o formato (serialização) das respostas geradas pelo retorno da operação. Ela recebe uma das opções fornecidas pelo enumerador WebMessageFormat, que estão descritas logo abaixo:

  • Json: Define e utiliza o formato JSON (JavaScript Object Notation). Utilizado em cenários AJAX.
  • Xml: Define e utiliza o formato Xml. Caso essa propriedade não seja definida, o Xml é o valor padrão.
UriTemplate Recebe uma string que representa um template para a URI a ser exposta e, posteriormente, invocada pelo cliente. Elas são utilizadas para descrever uma forma como a URI deve ser invocada, contemplando inclusive possíveis parâmetros. Vale lembrar que essa propriedade é customizada baseando-se fortemente na assinatura da operação (método).

Para finalizarmos os atributos de configuração, ainda temos o atributo WebInvokeAttribute. Este atributo deve ser aplicado a operações que devem estar disponíveis via operações HTTP, como POST, PUT ou DELETE. Este atributo fornece as mesmas propriedades do atributo WebGetAttribute, incluindo mais uma propriedade chamada Method, descrita detalhadamente através da tabela abaixo:

Propriedade Descrição
Method Essa propriedade recebe uma string contendo o método associado com o tipo da operação, como POST, PUT ou DELETE. Quando essa propriedade não é definida, o valor padrão é POST.

Com o conhecimento destes dois principais atributos, neste momento devemos criar a Interface do contrato de serviço que será exposto. O contrato de exemplo terá apenas dois métodos: Recuperar e Adicionar que, já são auto-explicativos. A idéia é que no primeiro deles, Recuperar, devemos permitir o acesso via HTTP GET e, na segunda opção, utilizar o padrão HTTP POST, para submeter um novo registro. O trecho de código abaixo exibe na integra o contrato a ser utilizado como exemplo, já com os devidos atributos configurados:

using System;
using System.ServiceModel;
using System.ServiceModel.Web;

[ServiceContract]
public interface IUsuario
{
    [OperationContract]
    [WebGet(UriTemplate = "RecuperarUsuarios/{quantidade}")]
    string[] Recuperar(string quantidade);

    [OperationContract]
    [WebInvoke(UriTemplate = "AdicionarNovoUsuario/{nome}/{email}")]
    bool Adicionar(string nome, string email);
}

Neste momento devemos nos atentar na propriedade UriTemplate. Como comentamos acima, ela defina a template da URI a ser invocada. É a partir dela que podemos especificar o nome da operação que o cliente deverá invocar – podendo ou não ser o mesmo nome do método; além disso, podemos também estruturar os parâmetros que esta operação recebe, organizando-os em QueryStrings ou até mesmo com barras “/”, como é o caso do exemplo. Há apenas um detalhe com relação aos parâmetros que devemos nos atentar: os parâmetros da operação deverão estar dentro da URI, envolvidos entre chaves. Automaticamente, quando isso for executado, o runtime do WCF se encarregará de extrair os valores do HTTP (do corpo ou da URL) e encaminhar para a respectiva operação, populando cada um desses parâmetros.

As mudanças no contrato acabam neste momento. A implementação do mesmo na classe concreta não tem nenhuma diferença. Contextualmente, temos uma classe chamada WebOperationContext que expõe uma propriedade chamada Current que retorna o mesmo tipo, representando o contexto da chamada atual. Basicamente essa classe disponibiliza quatro propriedades: IncomingRequest, IncomingResponse, OutgoingRequest e OutgoingResponse que, como os próprios nomes dizem, trazem informações a respeito do contexto da requisição e da resposta referente a execução da operação corrente.

Em nível de execução, temos algumas pequenas mudanças que precisamos nos atentar. A primeira e mais notável é com relação a criação do hosting que será utilizado para expor o serviço. Ao contrário de outros serviços que são expostos para diversos outros bindings, aqui utilizaremos o WebServiceHost ao invés do ServiceHost. Basicamente esse host mais especializado herda diretamente da classe ServiceHost, customizando o método OnOpening, desabilitando qualquer acesso aos metadados ou página de suporte; cria e configura endpoints para todos os tipos do contrato com o binding WebHttpBinding e adiciona o WebHttpBehavior para todos os endpoints para que as operações Get e Invoke trabalhem sem qualquer configuração adicional.

Outro detalhe importante é com relação ao tipo de binding. Dentro do Assembly System.ServiceModel.Web.dll é disponibilizado um novo binding, chamado de WebHttpBinding. Este binding permite expor os serviços através de requisições HTTP ao invés da criação do envelope SOAP. A utilização deste binding é implícita quando se utiliza o WebServiceHost, pois ele internamente o cria. É importante dizer que nada impede de criar uma instância da classe ServiceHost e configurá-la para expor o serviço via HTTP e, para isso, será necessário que explicitamente você crie um endpoint definindo como binding uma instância da classe WebHttpBinding.

Com essas considerações, as configurações do host ficam muito simples. Basta instanciar a classe WebServiceHost, especificando o nome da classe que implementa o contrato e também o endereço HTTP em que o serviço estará disponível. O código abaixo ilustra essa configuração:

using System;
using System.ServiceModel;
using System.ServiceModel.Web;

using (WebServiceHost host = 
    new WebServiceHost(typeof(Usuarios), new Uri[] { new Uri("http://localhost:8892") }))
{
    host.Open();
    Console.ReadLine();
}

Já que estamos com o host devidamente configurado, podemos iniciá-lo e invocar o endereço a partir do navegador para que possamos visualizar o resultado. De acordo com a configuração que efetuamos no contrato (Interface), podemos invocar a operação RecuperarUsuarios através do seguinte endereço: http://localhost:8892/RecuperarUsuarios/10. Podemos notar de que acordo com a especificação colocada na propriedade UriTemplate do atributo WebGetAttribute, depois do nome do método devemos especificar a quantidade. O resultado é mostrado abaixo com as duas possibilidades de saída (Xml e JSON):

ResponseFormat = WebMessageFormat.Json
[
    "Usuario 1","Usuario 2","Usuario 3","Usuario 4","Usuario 5",
    "Usuario 6","Usuario 7","Usuario 8","Usuario 9","Usuario 10",
]

ResponseFormat = WebMessageFormat.Xml
<ArrayOfstring>
  <string>Usuario 1</string> 
  <string>Usuario 2</string> 
  <string>Usuario 3</string> 
  <string>Usuario 4</string> 
  <string>Usuario 5</string> 
  <string>Usuario 6</string> 
  <string>Usuario 7</string> 
  <string>Usuario 8</string> 
  <string>Usuario 9</string> 
  <string>Usuario 10</string> 
</ArrayOfstring>

A partir do navegador podemos nos certificar de que o retorno está conforme o esperado. Podemos agora, através do próprio .NET criarmos código para efetuar a requisição para este mesmo método e recuperar o retorno a partir de uma aplicação .NET (ou qualquer outra, desde que faça o acesso via HTTP para o serviço). Basicamente temos que recorrer a classes já bastante conhecidas, como é o caso da WebRequest e WebResponse que formula a requisição e trata a resposta, respectivamente. O trecho de código abaixo como podemos proceder para invocar o método RecuperarUsuarios a partir da aplicação .NET:

using System;
using System.IO;
using System.Net;

WebRequest request =
    WebRequest.Create("http://localhost:8892/RecuperarUsuarios/10");

using (WebResponse response = request.GetResponse())
{
    using (StreamReader sr = new StreamReader(response.GetResponseStream()))
    {
        Console.WriteLine(sr.ReadToEnd());
    }
}

Em nenhum dos testes acima invocamos o segundo método, AdicionarNovoUsuario. Isso porque, pelo fato desta operação estar decorada com o atributo WebInvokeAttribute ele obrigatoriamente precisa ser invocada através do método POST do HTTP. Para isso, precisamos mudar o código acima para que atenda a essa necessidade.

Basicamente o que precisamos fazer é definir as propriedades Method, ContentLength e ContentType da classe WebRequest. Na primeira propriedade devemos especificar o tipo da requisição que, neste caso, será “POST”; já a propriedade ContentLength precisamos especificar o comprimento dos dados que farão parte da respectiva requisição e, finalmente, a propriedade ContentType especificamos o tipo de conteúdo que está sendo enviado. O código que temos acima muda ligeiramente para:

using System;
using System.IO;
using System.Net;

WebRequest request =
    WebRequest.Create(
        "http://localhost:8892/AdicionarNovoUsuario/Israel/israel@projetando.net"
    );

request.Method = "POST";
request.ContentLength = 0;

using (WebResponse response = request.GetResponse())
{
    using (StreamReader sr = new StreamReader(response.GetResponseStream()))
    {
        Console.WriteLine(sr.ReadToEnd());
    }
}

RAW do HTTP POST

Este código funciona bem, mas quando desejamos enviar o conteúdo através do RAW do HTTP POST (que envia a requisição com o content-type definido como “application/x-www-form-urlencoded”), demandará algumas mudanças, inclusive em nível de contrato do serviço. Para que isso seja possível, primeiramente precisamos mudar a assinatura do método da operação; ao invés de aceitar os parâmetros tradicionais, teremos que fazê-la receber um objeto do tipo Stream. A mesma interface que utilizamos acima, para suportar esse novo modelo, deve ficar da seguinte forma:

using System;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Web;

[ServiceContract]
public interface IUsuario
{
    [OperationContract]
    [WebGet(UriTemplate = "RecuperarUsuarios/{quantidade}")]
    string[] Recuperar(string quantidade);

    [OperationContract]
    [WebInvoke(UriTemplate = "AdicionarNovoUsuario")]
    bool Adicionar(Stream s);
}

Além dessa mudança, a sua implementação também é um pouco diferente. O Stream que é passado retorna todos os dados contidos no corpo da requisição HTTP. Isso nos obrigará a fazer um parser deste conteúdo e, felizmente, há uma classe dentro do .NET que, dado uma string, retorna uma coleção de chave-valor com essas informações. Abaixo é exibido apenas a implementação do método que é alvo da discussão por questões de espaço:

using System;
using System.IO;
using System.Web;
using System.Collections.Generic;
using System.Collections.Specialized;

public class Usuarios : IUsuario
{
    public bool Adicionar(Stream s)
    {
        NameValueCollection coll = 
            HttpUtility.ParseQueryString(new StreamReader(s).ReadToEnd());

        string nome = coll["nome"];
        string email = coll["email"];

        //adicionar em algum repositório

        return true;
    }
}

Infelizmente neste momento, a referência para o Assembly System.Web.dll se faz necessário porque a classe HttpUtility está definida dentro dele. A sua utilidade é justamente fazer o parser em uma string, retornando a coleção com chave-valor e, conseqüentemente, podendo utilizá-la da forma que acharmos conveniente.

Finalmente, para que seja possível executar o serviço a partir de uma aplicação, precisamos modificar a forma como passamos os parâmetros para a mesma. É necessário escrevermos o conteúdo que será enviado no corpo da requisição. Primeiramente precisamos definir em um formato de chave-valor todos os parâmetros que o método exige. Depois disso, com a string totalmente formada, utilizando & para separar cada um dos parâmetros, precisamos convertê-la em um array de bytes e atribuirmos na requisição. O código abaixo exibe tal configuração:

using System;
using System.IO;
using System.Net;
using System.Text;

string parameters = "nome=Israel Aece&email=israel@projetando.net";
byte[] body = Encoding.Default.GetBytes(parameters);

WebRequest request =
    HttpWebRequest.Create("http://localhost:8892/AdicionarNovoUsuario");

request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = body.Length;
request.GetRequestStream().Write(body, 0, body.Length);

using (WebResponse response = request.GetResponse())
{
    using (StreamReader sr = new StreamReader(response.GetResponseStream()))
    {
        Console.WriteLine(sr.ReadToEnd());
    }
}

Além da exigência de definir a propriedade ContentType com “application/x-www-form-urlencoded”, a diferença mais considerável no código acima é com relação ao método GetRequestStream. Este método retorna uma instância de um Stream que representa o corpo da requisição e é dentro dele que devemos escrever o conteúdo (os parâmetros) que a operação irá necessitar. Automaticamente o WCF é capaz de enviar este Stream para o serviço e, internamente, extraímos o conteúdo do mesmo como vimos mais acima.

Conclusão: Este artigo explica detalhadamente como podemos expor nossos serviços para serem invocados a partir de uma URL, sem a necessidade do overhead do protocolo SOAP. Muitas ferramentas que a Microsoft está criando, fazem uso deste recurso que, em um primeiro momento, apesar de parecer pouco útil, é extremamente válido e flexível quando precisamos expor os serviços e/ou operações a partir do HTTP, como é o caso de Blogs, RSS, etc.

RestFull.zip (139.05 kb)

Elemento readerQuotas

Quando serviço WCF, em sua configuração padrão, envia ou recebe mensagens com um conteúdo grande, é provável que exceções sejam disparadas devido a este conteúdo, pois ele excede o valor máximo permitido. Isso é útil para evitar um possível DoS.

Através do elemento readerQuotas voce pode especificar algumas configurações, como é o caso da propriedade maxStringContentLength, aumentando ou diminuindo (de acordo com a sua necessidade) o número máximo de caracteres que é permitido em um elemento XML.

Um detalhe importante é que essa configuração que pode ser realizada tanto do lado do servidor (para evitar que um conteúdo maior que X seja enviado pelo cliente) quanto pelo cliente (para evitar que um conteúdo maior que X seja retornado pelo servidor). Como essa configuração não está contemplada dentro do contrato (WSDL) do serviço, então fica a cargo do desenvolvedor especificar um valor, diferente do padrão, caso queira aceitar um conteúdo maior do que o especificado que é, por padrão, 8192.

(De)Serialização de Objetos em WCF

Objetos que são enviados ou recebidos em serviços WCF sofrem o processo de serialização e deserialização. Quando os métodos de um determinado serviço recebem ou devolvem objetos complexos, então estes também sofrem o mesmo processo.

Quando o cliente faz a referencia para este serviço, a IDE do Visual Studio .NET é capaz de ler os metadados e criar uma representação desta classe do lado do cliente, podendo agora, instanciar e enviar este objeto para um determinado método, ou ainda, receber de um dos métodos do serviço tal objeto. Como temos uma classe criada do lado do cliente, se ele desejar, o mesmo pode adicionar novas propriedades a mesma e, consequentemente, quando a instancia dessa classe for submetida para o serviço, essas novas propriedades não encontrarão uma correspondente do lado do servidor durante o processo de deserialização.

Felizmente a Microsoft disponibilizou a interface IExtensibleDataObject que fornece uma estrutura para armazenar os dados extras (propriedades marcadas com o atributo DataContractAttribute) encontrados durante o processo de deserialização. Essa interface fornece uma única propriedade chamada ExtensionData. Segundo as boas práticas, sempre é importante que se implemente essa interface em todos as classes que são decoradas com o atributo DataContractAttribute. Se notarmos, independentemente de implementar essa interface ou não nas classes que estão no serviço, ao fazermos a referencia no projeto cliente, automaticamente, a classe criada pela IDE já implementa tal interface, justamente para permitir que o servidor mande novas propriedades que o cliente ainda não possui.

O fato de implementar essa interface não quer dizer que voce obrigatoriamente precisa manter esses dados. É perfeitamente possível desconsiderar qualquer propriedade, caso não encontre uma correspondente durante a deserialização. Para isso, voce tem algumas possibilidades. A primeira delas é definir para True a propriedade IgnoreExtensionDataObject do atributo ServiceBehaviorAttribute, o que refletirá em todas as operações que o serviço fornece. O exemplo abaixo ilustra isso:

[ServiceBehavior(IgnoreExtensionDataObject = true)]
public class Usuarios : IUsuario { }

A outra alternativa é via arquivo de configuração, que te dará uma maior flexibilidade, já que voce pode ligar ou desligar sem precisar recompilar a aplicação. Para isso, utilizamos o elemento dataContractSerializer:

<dataContractSerializer ignoreExtensionDataObject=”true” />

Finalmente, se desejar aplicar esse comportamento apenas para um determinado método, precisamos recorrer a classe DataContractSerializerOperationBehavior. Mas antes de mais nada, precisamos inicialmente recuperar a operação que desejamos manipular e, em seguida, criarmos a instancia da classe DataContractSerializerOperationBehavior e, para finalizar, adicionamos a mesma na coleção de behaviors da operação em questão. O trecho de código abaixo ilustra esse processo:

private static void SetIgnoreExtensionDataObject(ServiceHost host, string operationName, bool ignore)
{
    ContractDescription cd = host.Description.Endpoints[0].Contract;
    OperationDescription od = cd.Operations.Find(operationName);

    DataContractSerializerOperationBehavior b =
        od.Behaviors.Find<DataContractSerializerOperationBehavior>();

    if (b == null)
    {
        b = new DataContractSerializerOperationBehavior(od);
        od.Behaviors.Add(b);
    }

    b.IgnoreExtensionDataObject = ignore;
}

WCF – Expondo componente COM+

Em 2002 a Microsoft lançou a plataforma .NET, visando o desenvolvimento de softwares e componentes. Apesar de suas evidentes inovações (como metadados, serialização e versionamento), ela é essencialmente uma tecnologia de componente, assim como o tradicional COM (Component Object Model); a plataforma .NET fornece meios (produtivos) para a construção destes componentes, baseando-se em código gerenciado, podendo escrevê-los sob qualquer linguagem considerada .NET, como as mais populares Visual Basic .NET ou Visual C#.

A plataforma .NET é a tecnologia mais recente da Microsoft para o desenvolvimento de componentes, e é intenção da Microsoft tornar o .NET a evolução do COM. Apesar da plataforma .NET ser consideravelmente mais fácil e simples de trabalhar, ela tem os mesmos problemas que o COM, ou seja, não possui seus próprios serviços de componentes, como gerenciamento de transações e concorrência (sincronização), dependência de plataforma, segurança, chamadas enfileiradas, etc.. Ambas tecnologias recorrem ao COM+ para conseguir resolver grande parte destes desafios. No mundo .NET, podemos fazer a referência ao Assembly System.EnterpriseServices.dll em nosso projeto e, conseqüentemente, criarmos componentes que suportem essas funcionalidades.

O sistema operacional como o Windows XP, Windows Vista ou até mesmo os sistemas operacionais de servidores trazem nativamente uma ferramenta administrativa para o gerenciamento dos componentes que estão hospedados e fazem o uso do COM+. Essa ferramenta, chamada Component Services, permite-nos, de forma visual, catalogar e gerenciar os componentes hospedados dentro de um computador específico, possibilitando a configuração de segurança, transação, mensageria, etc. e, um dos recursos existentes nesta mesma ferramenta (em conjunto com a versão 1.5 do COM+), é a possibilidade de expor o componente via XML Web Service, ou seja, permitir que o mesmo seja acessado através do protocolo HTTP. Uma vez habilitado, ela cria um diretório virtual dentro do IIS, colocando dentro dele um ponto de acesso até o WSDL (Web Service Description Language) com a descrição do serviço. Com isso, qualquer cliente pode adicionar a referência a este serviço e consumí-lo dentro da aplicação através da internet (HTTP + SOAP). A imagem abaixo exibe a tela de configuração para expor um componente COM+ via XML Web Service:

Figura 1 – Expondo o componente COM+ como XML Web Service.

Uma consideração importante a ser feita é com relação a segurança: como o diretório virtual não estará protegido por SSL, então é necessário que todas as opções de segurança estejam desabilitadas. Através da imagem acima podemos notar que no campo SOAP VRoot especificamos o nome do diretório virtual a ser criado no IIS e, quando clicamos no botão OK, as seguintes tarefas são executadas:

  • O diretório virtual com o nome especificado no campo SOAP VRoot é criado dentro do IIS, apontando fisicamente para um diretório com o mesmo nome criado em WindowsSystem32COMSOAPVRoots.

  • Dentro deste diretório são criados dois arquivos: Default.aspx e Web.Config. Estes arquivos são utilizados para localizar e configurar o Web Service.

Com o surgimento do WCF, uma plataforma de comunicação unificada, a Microsoft não se esqueceu do legado, ou seja, de componentes grandes e complexos hospedados no COM+ e, possibilita a utilização do WCF para expor esse componente através do HTTP. Ao contrário do que vimos anteriormente, não precisamos recorrer ao Component Services para isso. Junto com o SDK do .NET Framework 3.X, a Microsoft disponibiliza uma ferramenta chamada Microsoft Service Configuration Editor que, dentre todas as funcionalidades disponibilizadas, uma delas é a possibilidade de integração de um componente COM+ a um serviço WCF. Analisaremos essa ferramenta e essa funcionalidade mais tarde, ainda neste artigo.

Para exemplificar este cenário, vamos criar um componente para hospedá-lo dentro do COM+. Para que isso seja possível, você precisa se atentar a algumas regras impostas pela plataforma .NET para que isso seja possível. O primeiro passo é a criação de um projeto do tipo Class Library para dar origem a uma DLL e, conseqüentemente, ser hospedada dentro do COM+. Depois disso, é necessário fazer a referência para o Assembly System.EnterpriseServices.dll. A primeira regra entra em cena neste momento: toda classe (componente) que será hospedada dentro do COM+ precisa, obrigatoriamente, herdar da classe base ServicedComponent.

Para disponibilizar o componente para o mundo COM, primeiramente deve-se desenhar o componente visando facilitar esse processo e, para isso, devemos explicitamente implementar Interfaces nos componentes que serão expostos. Isso é necessário porque componentes COM “não podem conter” os membros diretamente e, sendo assim, devem ter as Interfaces implementadas. Apesar do COM poder gerar a Interface automaticamente, é melhor criarmos isso manualmente, o que permitirá um maior controle sob o componente e suas formas de atualização. Quando os componentes COM geram automaticamente a sua Interface, você não pode fazer nenhuma mudança em sua estrutura pública. Isso se deve porque componentes COM são imutáveis. Se você romper essa regra, o componente deixará de ser invocado.

Com essas primeiras regras, já conseguimos evoluir para a criação do componente. Abaixo são mostradas a Interface e a classe que a implementa. Reparem que nem a classe e nem a Interface nada sabem sobre WCF.

using System;
using System.Runtime.InteropServices;

namespace Library
{
    [Guid("11EF17BF-C276-41ea-AEA1-C371CF7704F1")]
    public interface IUsuario
    {
        bool Adicionar(string nome, string email);
        void Excluir(int id);
    }
}

using System;
using System.EnterpriseServices;
using System.Runtime.InteropServices;

namespace Library
{
    [Guid("14671242-00FF-4b3c-8F1C-397155857FB7")]
    [ClassInterface(ClassInterfaceType.None)]
    public class Usuarios : ServicedComponent, IUsuario
    {
        public bool Adicionar(string nome, string email)
        {
            //Adicionar a algum repositório
            return true;
        }

        public void Excluir(int id)
        {
            //Excluir do repositório
        }
    }
}

A primeira consideração a fazer é com relação a herança da classe Usuarios. Podemos reparar que ela herda diretamente da classe ServicedComponent e, além disso, implementa a Interface IUsuario. Podemos notar que tanto a classe quando a Interface estão decoradas com o atributo GuidAtrribute. Podemos recorrer a esta técnica para especificar o GUID para cada um deles, ao invés de deixar isso a cargo do registro do componente no COM+. Outro atributo que está decorando a classe Usuarios é o ClassInterfaceAttribute. Quando ele está definido como None, o COM não criará uma Interface padrão para a classe. Neste caso você deverá, explicitamente, fornecer uma Interface para ser implementada na classe que será exposta para o mundo COM.

Nota: Por padrão, quando criamos um projeto do tipo Class Library, o arquivo AssemblyInfo possui um atributo chamado ComVisibleAttribute definido como False. Isso evitará que o componente seja exposto via COM e, conseqüentemente, você não conseguirá hospedá-lo dentro do COM+. Para evitar maiores surpresas, certifique-se de que ele esteja com esse atributo definido como True.

Finalmente, para concluir a criação do componente de exemplo, apenas precisamos definir um Strong Name para ele e instalá-lo dentro do Global Assembly Cache – GAC. Depois disso, para efetivamente instalar dentro do COM+, podemos recorrer ao utilitário regsvcs.exe ou através do próprio Component Services que fornece um assistente que nos auxilia neste processo.

Até o presente momento não abordamos nada exclusivamente de WCF. Todo o processo até então já era conhecido desde a versão 1.0 do .NET Framework. A partir daqui, para conseguirmos expor esse componente via WCF, vamos recorrer ao utilitário Microsoft Service Configuration Editor. Indo até o menu File, Integrate e, em seguida, COM+ Application, um assistente é inicializado para nos auxiliar neste processo. Basicamente o que ele fará é nos conduzir desde a seleção do componente COM+ até a criação do diretório virtual dentro do IIS. A imagem abaixo ilustra a tela de seleção do componente a ser exposto:

Figura 2 – Selecionando o componente COM+.

Ao selecionarmos a Interface a ser exposta, o próximo passo consiste na seleção dos métodos que serão disponibilizados através do serviço. A imagem abaixo exibe essa tela de seleção de métodos e podemos notar que são exatamente os métodos que criamos na Interface IUsuario, um pouco mais acima.

Figura 3 – Selecionando os métodos a serem expostos.

Ao avançar para o próximo passo, o utilitário nos dá a opção para selecionarmos qual será o tipo de host utilizado pelo WCF para disponibilizarmos um ponto de acesso. Se optarmos por COM+ hosted (utiliza um processo Dllhost.exe), isso permitirá o acesso via TCP, HTTP ou Named Pipe mas obrigará que o serviço já esteja rodando para responder as requisições; já no modo Web hosted podemos utilizar o IIS como host do serviço, podendo ser ativado somente quando a primeira requisição for feita e, para o nosso exemplo, é esta opção que utilizaremos. Além disso, ainda há a possibilidade de adicionarmos ao host um endpoint para acesso aos metadados do serviço. A imagem abaixo ilustra essas opções comentadas:

Figura 4 – Escolhendo o modelo de host.

Como optamos pelo modelo Web hosted, o próximo passo exige que informemos o nome do diretório virtual existente onde a estrutura necessária para acessar o serviço será armazenada. É importante que você crie este diretório virtual antes de iniciar este assistente. Para o exemplo, foi criado um diretório virtual chamado ServicoDeUsuarios e, como podemos ver, ele está listado na tela abaixo:

Figura 5 – Selecionando o diretório virtual.

Finalmente depois de todos esses passos rigorosamente configurados, dois arquivos são criados dentro do diretório virtual exigido no último passo do assistente. Os arquivos são: Library.Usuarios.svc e Web.Config. O nome do primeiro arquivo é o full-name da classe que criamos no COM+ acrescido da extensão *.svc que, por sua vez, caracteriza um serviço WCF. Se abrirmos este arquivo em qualquer editor de texto, veremos que ele tem uma única linha, que é a diretiva @ServiceHost. Abaixo consta o conteúdo deste arquivo:

<%@ServiceHost
    Factory="System.ServiceModel.ComIntegration.WasHostedComPlusFactory" 
    Service="{14671242-00ff-4b3c-8f1c-397155857fb7},{629d362f-d757-4f97-86e6-e1901a522607}" %>

O primeiro ponto de análise é o atributo Factory. Esse atributo especifica o tipo que será a “factory” usada por instanciar a classe responsável para servir de host para o serviço. A classe WasHostedComPlusFactory que está dentro do namespace System.ServiceModel.ComIntegration é utilizada quando a implementação do serviço é um componente hospedado no COM+. Já o atributo Service especifica o tipo do serviço que será exposto. Geralmente colocamos aqui o nome da classe que implementa o contrato mas, como estamos expondo um componente que está no COM+, o valor deste atributo recebe uma informação um pouco diferente da tradicional. Neste caso, o atributo Service está recebendo duas GUIDs onde a primeira representa o GUID do componente (classe Usuarios), e a segunda especifica a GUID da aplicação COM+. É importante que essas GUIDs estejam sincronizadas para que tudo funcione da forma correta.

Ainda temos o arquivo Web.Config que também foi automaticamente gerado. Esse arquivo também sofre ligeiras mudanças em relação a uma configuração tradicional de um serviço exposto sob WCF. Abaixo consta o conteúdo do arquivo gerado (algumas linhas foram omitidas por questões de espaço):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ComServiceMexBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <comContracts>
      <comContract 
          contract="{11EF17BF-C276-41EA-AEA1-C371CF7704F1}"
          name="IUsuario" 
          namespace="http://tempuri.org/11EF17BF-C276-41EA-AEA1-C371CF7704F1"
          requiresSession="true">
        <exposedMethods>
          <add exposedMethod="Adicionar" />
          <add exposedMethod="Excluir" />
        </exposedMethods>
      </comContract>
    </comContracts>
    <services>
      <service 
        behaviorConfiguration="ComServiceMexBehavior" 
        name="{629D362F-D757-4F97-86E6-E1901A522607},{14671242-00FF-4B3C-8F1C-397155857FB7}">
        <endpoint 
            address="IUsuario" 
            binding="wsHttpBinding" 
            bindingConfiguration="comNonTransactionalBinding"
            contract="{11EF17BF-C276-41EA-AEA1-C371CF7704F1}" />
        <endpoint 
            address="mex" 
            binding="mexHttpBinding" 
            bindingConfiguration=""
            contract="IMetadataExchange" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

O que chama a atenção no arquivo Web.Config gerado é com relação ao nome do serviço exposto e, em especial, ao contrato que é exposto através do endpoint. Da mesma forma que o arquivo Library.Usuarios.svc, o Web.Config não faz referência diretamente ao nome da classe ou da Interface, mas sim aos GUIDs que estão relacionados às mesmas. O serviço exposto é configurado com o GUID que representa a aplicação dentro do COM+ em conjunto com o GUID que representa o componente (a classe Usuarios). Da mesma forma, o contrato exposto não é o nome da Interface que criamos (IUsuario), mas sim o GUID que está relacionado a ela.

Para finalizar, o arquivo Web.Config ainda conta com um novo elemento chamado comContracts que é usado para a integração entre o WCF e os componentes hospedados no COM+. Basicamente esta seção permite configurarmos algumas propriedades (como namespace, sessions, etc.) que, a princípio, deveriam estar no componente mas, como ele não foi desenhado para WCF, você tem a chance de customizar isso neste arquivo.

Depois de todo esse processo, o que precisamos neste momento é apenas fazer a referência deste serviço em nossas aplicações cliente e, via proxy, invocarmos o serviço como se fosse um tradicional serviço WCF. Para efeitos de testes, podemos acessar o endereço HTTP através do navegador e, se tudo correr bem, você deverá visualizar uma página (como mostrado abaixo) com o endereço até o WSDL do serviço.

Figura 6 – Efetuando o teste do serviço.

Conclusão: Como podemos notar no decorrer deste serviço, apesar da Microsoft ter criado uma unificada e consistente plataforma de comunicação, ela não se esqueceu das várias aplicações que foram construídas sob tecnologias anteriores, como é o caso dos XML Web Services e componentes hospedados dentro do COM+. Para aqueles que já possuem o componente, basta recorrer ao utilitário Microsoft Service Configuration Editor para configurar como o mesmo será exposto e, finalmente, desfrutar de todo o poder e flexibilidade que o WCF proporciona.

CompenenteCOMPlus.zip (55.84 kb)

Funcionamento do WSDualHttpBinding no cliente

Uma das possibilidades dentro do WCF é a capacidade que o serviço tem de invocar um callback para o cliente, permitindo que este método client-side seja executado e, conseqüentemente, o cliente possa interagir com o serviço, simulando os tradicionais eventos.

Nativamente os bindings NetTcpBinding e NetNamedPipeBinding fazem isso nativamente, devido aos respectivos protocolos utilizados. Além desses, há um binding chamado do WSDualHttpBinding que tem essa mesma finalidade mas, como o próprio nome diz, utilizando o protocolo HTTP. A imagem abaixo ilustra esse processo acontecendo entre o cliente e o serviço:

Quando você faz o uso do WSDualHttpBinding, o WCF utilizará um canal diferente para invocar o callback. Internamente, o que acaba sendo feito do lado do cliente – por parte do runtime do WCF – é a criação de um “endpoint” (incluindo uma porta disponível) para que o serviço relacionado consiga invocá-lo. Esse endereço temporário criado pelo WCF, é registrado sob o Http.sys (mais detalhes aqui) que é um componente de baixo nível e que faz parte do sistema de comunicação do Windows. Sendo assim, qualquer requisição que chegar para esse “endpoint”, o Http.sys encaminhará para o runtime do WCF que, finalmente, irá disparar a classe que implementa a interface de callback que, por sua vez, foi fornecida pelo contrato do serviço.

Finalmente, todo esse processo ocorre de forma transparente, permitindo que aplicações distribuídas, rodando como cliente WCF, possam disponibilizar um ponto de comunicação para essa interação.