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.

Habilitando o Health Monitoring no WCF

O ASP.NET 2.0 introduziu uma nova API chamada de Health Monitoring. Esta API permite-nos, de forma flexível, ajustar o log/auditoria de uma aplicação ASP.NET. Como sabemos, podemos hospedar um serviço WCF dentro de uma aplicação ASP.NET e, sendo assim, poderíamos utilizar esta mesma API para conseguirmos logar possíveis eventos (isso pode incluir exceções) que o serviço venha a disparar.

Basicamente o que precisamos fazer é criar um evento customizado para o WCF e configurá-lo como já acontecia (isso é mostrado em detalhes neste artigo). Uma vez que o evento é criado, podemos anexá-lo dentro do runtime do WCF, mas isso exige algumas técnicas. A primeira delas é implementar a Interface IErrorHandler que, por sua vez, disponibiliza dois métodos: HandleError e ProvideFault. O método HandleError é sempre invocado quando alguma exceção ocorre dentro do serviço e, é fornecido para esse método um parametro do tipo Exception que representa o erro ocorrido. É no interior deste método que devemos criar o evento, definir a mensagem e, finalmente, invocar o método Raise para persistí-lo no provider especificado na configuração do Health Monitoring (mais adiante).

Para concluir, ainda é necessário implementar a Interface IServiceBehavior que fornece um mecanismo para interceptar e inserir comportamentos específicos ao serviço como um todo. Nos métodos fornecidos por essa Interface, utilizaremos o ApplyDispatchBehavior, que nos permitirá adicionar extensões, como error handlers (tema do post), interceptors, etc.. Abaixo a classe que implementa essas Interfaces e também a Interface do contrato do serviço:

public class ServiceImpl : IData, IServiceBehavior, IErrorHandler
{
    public string Teste(string value)
    {
        if (string.IsNullOrEmpty(value))
            throw new FaultException<ArgumentNullException>(new ArgumentNullException(“value”));

        return “Ola ” + value;
    }

    public void AddBindingParameters(…) { }

    public void ApplyDispatchBehavior(…)
    {
        foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
            dispatcher.ErrorHandlers.Add(this);
    }

    public void Validate(…) { }

    public bool HandleError(Exception error)
    {
        new WCFErrorEvent(error.ToString(), null, 0).Raise();
        return true;
    }

    public void ProvideFault(…) { }
}

Para o exemplo, eu implementei as duas Interfaces na mesma classe de serviço, o que não é muito aconselhável, já que polui muito o código, misturando características do runtime com a regra de negócio. Se notarem, dentro do método ApplyDispatchBehavior adicionamos em todos na coleção de dispatcher channels a instancia corrente da classe para que, no momento em que o erro for disparado, esse tratamento/log seja efetuado.

Para finalizar, apenas resta especificar a configuração do Health Monitoring dentro do arquivo Web.Config da aplicação para que finalmente todos os eventos sejam persistidos.

<healthMonitoring enabled=”true”>
  <eventMappings>
    <add name=”WCF Error Event” type=”MyService.WCFErrorEvent”/>
  </eventMappings>
  <rules>
    <clear/>
    <add
      name=”All Errors Default”
      eventName=”WCF Error Event”
      provider=”EventLogProvider”
      profile=”Default”
      minInterval=”00:01:00″/>
  </rules>
</healthMonitoring>

WCF – Partial Trust

Uma das configurações mais importantes que se deve realizar em uma aplicação é a segurança, mais precisamente, que tipo de permissão devemos conceder à mesma para que ela possa ser executada. O tipo de permissão que me refiro aqui não tem a ver com a identidade do usuário, mas sim as permissões que o código (Assembly) tem para acessar recursos da máquina onde o mesmo está sendo executado.

Esse tipo de configuração foi introduzida desde a primeira versão do .NET Framework e é chamada de Code Access Security – CAS. Baseada no conceito de permissões, onde cada uma delas mapeia para um recurso (Message Queue, File System, SQL Server, etc.) da máquina, são através destas permissões que dizemos ao Runtime Security Policy (de forma declarativa ou imperativa), quais direitos nosso código tem de executar, ou melhor, a quais recursos ele terá acesso.

Se você cria qualquer aplicação .NET e não altera nenhuma configuração de segurança, ele será executado em Full Trust, ou seja, terá acesso irrestrito a qualquer recurso da máquina onde está atualmente sendo executado. Apesar disso ser muito comum, não é ideal. Uma premissa básica na segurança é jamais conceder direitos desnecessários a um código para desempenhar suas funções. Quando você opta por mudar a segurança padrão imposta pelo .NET, quer dizer que você está executando a aplicação em Partial Trust, ou seja, em um ambiente em que você não terá acesso a todos os recursos, mas sim, o essencial para a aplicação poder trabalhar.

Na primeira versão do WCF (Windows Communication Foundation) – .NET Framework 3.0 – ele não era suportado em ambientes que estavam sob Partial Trust, o que obrigava muitos clientes a conceder mais direitos do que o necessário para poder executar/invocar um serviço escrito em WCF. Depois de muitas requisições, a Microsoft decidiu afrouxar essa segurança com o lançamento do .NET Framework 3.5, permitindo (com várias restrições) que serviços sejam invocados a partir de um ambiente parcialmente confiável. Isso obrigou a Microsoft a decorar os assemblies System.ServiceModel.dll e System.ServiceModel.Web.dll com o atributo AllowPartiallyTrustedCallers que, como o nome diz, permite que o assembly seja invocado por aplicações que estão sendo executadas em um ambiente mais restrito.

Entre as limitações que o WCF 3.5 impõe, temos: permite somente os bindings BasicHttpBinding, WsHttpBinding (sem segurança ou com segurança a nível de transporte) e WebHttpBinding serem invocados a partir de uma aplicação que está sendo executada em ambiente parcialmente confiável; além disso, serviços WCF que estão rodando em aplicações de middle tier também podem invocar requisições para outros serviços, desde que tenha a sua respectiva WebPermisson, que concede direito de acesso a esse recurso. Todos os outros bindings, não HTTP, demandarão full trust de seus chamadores, obrigando-nos a conceder ao cliente mais direitos do que ele deveria ter para poder trabalhar. Para uma lista completa de todas as restrições, você pode consultar este link.

Mas e quando necessitamos referenciar em nossa aplicação parcialmente confiável, um serviço WCF que expõe apenas um endpoint com o binding definido como NetTcpBinding? Uma alternativa interessante é criar um proxy, isolando toda a chamada para este serviço. E no que consiste um proxy? Basicamente trata-se de uma DLL que conterá uma classe que servirá como um wrapper para a interface do serviço a ser acessado. Quando referenciamos o serviço diretamente na aplicação, automaticamente a IDE do Visual Studio se encarrega de criar a classe (também denominada proxy) para que possamos invocar os respectivos métodos do serviço localmente e que, durante a execução, será delegado ao serviço.

Para o nosso cenário isso não será a melhor saída. O que devemos fazer é utilizar o utilitário svcutil.exe que, entre suas utilidades, é capaz de gerar uma classe baseado no WSDL extraído do mesmo. A linha abaixo ilustra como devemos passar os parâmetros para o svcutil.exe para que o mesmo possa gerar a classe correspondente (executando a partir do prompt do Visual Studio .NET):

C:>svcutil net.tcp://localhost:9000/Mex /out:C:Proxy.cs

Uma vez que a classe é criada, criaremos um projeto do tipo Class Library e adicionaremos a classe recém criada, Proxy.cs, dentro deste projeto. Além disso, é também necessário fazer a referência para o assembly System.ServiceModel.dll. Depois deste processo, precisamos ainda fazer algumas configurações em nível de assembly; como essa DLL deverá ser consumida por um projeto que está sendo executado em um ambiente parcialmente confiável, adicionar no arquivo AssemblyInfo.cs o atributo AllowPartiallyTrustedCallers; finalmente, será necessário definir um Strong Name para essa DLL (veremos mais tarde, ainda neste artigo o motivo disso). O trecho de código abaixo ilustra como devemos proceder para definir o atributo APTCA:

using System.Security;

[assembly: AllowPartiallyTrustedCallers]

Para finalizar, é necessário abrir o arquivo Proxy.cs e, no ínicio da classe que representa o serviço do lado do cliente (ClientBase), e especificar o atributo PermissionSetAttribute, assim como é mostrado abaixo:

using System.Security;
using System.Security.Permissions;

[PermissionSetAttribute(SecurityAction.Assert, Name = "FullTrust")]
public partial class Service1Client : System.ServiceModel.ClientBase<IService1>, IService1
{
    //Implementação
}

O atributo PermissionSetAttribute irá permitir a definição de uma determinada ação para uma permission set (conjunto de permissões) específica. No exemplo acima, estamos especificando a ação Assert (via enumerador SecurityAction) sob a permission set predefinida, que é a FullTrust. A utilização da ação/método Assert deve ser analisada com muito cuidado, pois é o mesmo que “Eu sei o que estou fazendo; confie em mim!”, e isso garantirá que a permissão seja concedida ao chamador mesmo que ele não tenha privilégio para isso.

Finalmente, a DLL está pronta para ser utilizada mas se, neste momento, a mesma for referenciada na aplicação que está sendo executada em ambiente parcialmente confiável, uma exceção será atirada, dizendo que não é possível consumir um serviço via TCP nesta aplicação. Neste caso, necessitamos adicionar essa DLL no GAC, pois os componentes que lá residem já ganham Full Trust. É importante dizer que, mesmo assim, é necessário o código acima, pois o stack walk também verificará a aplicação Web que chama o componente e essa, por sua vez, não terá a permissão necessária e, consequentemente, a exceção será atirada. Esse conceito que utilizamos aqui também é conhecido como Sandboxing.

Importante: Apesar desta técnica funcionar, ela tem um ponto negativo: suprimindo a security demand, que é o processo de avaliação dos chamadores para se certificar que todos possuem tal permissão, permitirá que qualquer cliente que execute sob ambiente parcialmente confiável chame qualquer serviço WCF. Isso permitirá a um cliente que não tenha permissão de acesso a algum recurso, como acesso TCP, aplicar um bypass nesta limitação. A solução para esse problema é criar uma classe que herde diretamente da classe base ClientBase, concedendo as permissões específicas, dependendo do tipo de binding.

Conclusão: Felizmente o WCF permite na versão 3.5 do .NET Framework o consumo de serviços em ambiente parcialmente confiáveis. Alguns bindings podem ser consumidos sem a necessidade de alguma configuração adicional, pois são acessíveis em ambientes parcialmente confiáveis; já outros bindings não podem ser acessados diretamente, e necessitam de um passo adicional, que é a criação do proxy, explicado neste artigo.

Duplex Services no ASP.NET

O WCF fornece uma funcionalidade chamada Duplex Services. Este tipo de serviço permite que o servidor invoque um método do lado do cliente através de callbacks.

Quando estamos trabalhando com Windows Forms, o consumo deste tipo de serviço não tem muito segredo, pois voce pode apontar para um método dentro do teu formulário ou até mesmo em uma classe do teu projeto que, quando chegar o momento, o runtime do WCF o invocará. No ASP.NET as coisas funcionam de forma diferente. Uma vez que voce requisita uma página ASPX, o ASP.NET cria o objeto correspondente a mesma, executa e, por fim, o descarta. Com isso, se apontarmos o callback de um serviço duplex para um método qualquer da página, muito provavelmente, quando chegar o momento do serviço executá-lo, ele já não existirá mais.

Para resolver, ou melhor, aguardar até que o callback seja disparado, voce pode utilizar um sincronizador, como o é o caso do ManualResetEvent que irá aguardar até que o callback seja disparado. O exemplo desta utilização é mostrado através do trecho de código abaixo:

public partial class _Default : Page, IServiceCallback
{
    private ManualResetEvent _mre = new ManualResetEvent(false);

    protected void Page_Load(object sender, EventArgs e)
    {
        using (ServiceClient proxy =
            new ServiceClient(new InstanceContext(this)))
        {
            proxy.Send(“Israel”);
            _mre.WaitOne();
        }
    }

    public void Notification(string message)
    {
        Response.Write(message);
        _mre.Set();
    }
}

Como podemos notar, o método WaitOne aguardará até que o método Set seja disparado que, por sua vez, está sendo invocado dentro do método de callback.