WCF – Orcas

Com o lançamento do primeiro Beta do Orcas, achei muito interessante duas novas features que o WCF fornecerá. Essas features são:

  • WebHttpBinding: Trata-se de um novo tipo de binding que permite a criação de um endpoint para ser consumido por aplicações AJAX. É basicamente algo semelhante ao BasicHttpBinding, com a exceção de que os dados são serializados em formato JSON (JavaScript Object Notation). É bom dizer que esta classe está dentro do namespace System.ServiceModel do Assembly System.ServiceModel.Web.dll.
    • Atentem-se ao blog do Luis Abreu que, possivelmente, ele estará mostrando alguma coisa sobre como consumir serviços WCF em ASP.NET AJAX.
  • WCF Tooling: Como sabemos, é necessário termos um hosting para disponibilizarmos serviços WCF, para que os mesmos possam ser consumidos. O WCF Tooling permite selecionarmos um tipo de projeto (project template) que já inclui um self-hosting. Isso quer dizer que, ao criar um serviço WCF, basta rodar o projeto via F5, que o mesmo já estará disponível para ser referenciado em outras aplicações.

Para maiores informações, consultem o link: http://blogs.msdn.com/mwinkle/archive/2007/02/28/wcf-and-wf-in-quot-orcas-quot.aspx

WCF – Integrando MembershipProvider e RoleProvider

Por padrão, serviços WCF utilizam as identidades e grupos do Windows para autenticação e autorização, respectivamente. Um dos grandes problemas é quando temos isso sendo disponibilizado através de uma aplicação web, pois iria requerer que todos os clientes que acessam via web estivessem devidamente cadastrados dentro do Windows; além disso, há o problema com relação aos grupos de usuários, já que muitas vezes não temos acesso para cadastrá-los e, quando isso não é um problema, podemos ter um problema adicional quando estivermos rodando em culturas de servidores diferentes.

Com isso, dificilmente uma aplicação ou serviços que são expostos para a internet utilizam as contas e grupos do Windows. A solução é que felizmente o ASP.NET 2.0 fornece uma infraestrutura completa para o gerenciamento de autenticação e autorização, chamada de Provider Model. Ela nos dá uma enorme flexibilidade, onde podemos trocar a fonte de dados/persistência e a aplicação continua trabalhando normalmente. Este provider é um módulo do software que estamos desenvolvendo que fornece uma interface genérica para uma fonte de dados, onde abstraem a base de dados. Além disso ser flexível, a qualquer momento podemos trocar a base de dados (essa arquitetura também é extensível) ou seja, se mais tarde quisermos customizar algo, podemos fazer isso sem maiores problemas.

Com toda essa infraestrutura já bem desenhada e sendo utilizada largamente no mercado, a Microsoft decidiu, em uma das formas de segurança de serviços WCF, integrar com esse modelo disponibilizado pelo ASP.NET 2.0. Este artigo já assume que você tenha conhecimento suficiente nesta infraestrutura e, em alguns pontos, vamos falar de alguns detalhes mais superficialmente. Se você ainda não estiver a vontade nisso, eu aconselho extremamente ler o artigo Entendendo e Implementando Segurança no ASP.NET 2.0 antes de prosseguir.

O transporte da mensagem

Quando estamos em um cenário de internet, as mensagens são enviadas para o serviço através de puro HTTP, porém é extremamente importante proteger o corpo e as credenciais do usuário durante essa “viagem”. A solução para isso é criptografar a mensagem para garantir a integridade e privacidade, utilizando a senha do usuário mas, mais uma vez por questões de segurança, o WCF não utiliza essa forma por ser vulnerável, pois o usuário pode ter uma senha pouco complexa e qualquer um que esteja monitorando a comunicação poderá interceptar e, conseqüentemente, “quebrar” a criptografia.

Para proteger a mensagem, o WCF utiliza um certificado do tipo X.509, que fornece uma proteção extremamente confiável. Este tipo de certificado também autentica unicamente o serviço para o cliente, trabalhando com duas chaves: uma pública e outra privada. Quando criptografamos algo com a chave pública, somente quem tiver a chave privada poderá decriptografar o seu conteúdo e, neste nosso cenário, o detentor da chave pública é o serviço WCF que, por sua vez, deverá manter a chave privada armazenada em algum local seguro para que a mesma não caia em mãos erradas.

A chave pública fica disponível no servidor que expõe o serviço e, sendo assim, qualquer cliente pode acessar os endpoints do serviço WCF e obter a chave pública. Como já sabemos, essa chave pública será utilizada pelo cliente para criptografar todas as mensagens que serão enviadas para o serviço que, por sua vez, quando receber a requisição, irá decriptografá-la utilizando a chave privada e utilizará as credenciais fornecidas pelo usuário para autenticá-lo e, conseqüentemente, permitir o acesso ao serviço.

Para o teste que vamos realizar no decorrer deste artigo, necessitamos de um certificado X.509 para utilizarmos durante a criação do serviço. Como não tenho nenhum certicado disponível, podemos criar um para testes. Para isso, utilizaremos um utilitário chamado Makecert.exe que é fornecido junto com o .NET Framework e pode ser encontrado no seguinte endereço: C:Program FilesMicrosoft Visual Studio 8SDKv2.0Bin. Este utilitário cria um par de chave pública e privada e armazena em um arquivo de certificado. Para criar o certificado, abra o prompt de comando do Visual Studio .NET 2005 (isso para evitar o path completo do utilitário) e digite:

makecert -sr LocalMachine -ss My -sky exchange -pe -a sha1 -n "CN=IACertificate" IACertificate.cer

Basicamente, através da linha acima, criamos um certificado chamado IACertificate e armazenamos ele em um arquivo chamado IACertificate.cer. Mas é importante dizer sobre cada um das opções que são passadas para o utilitário Makecert.exe, opções que são detalhadas através da tabela abaixo:

Opção Descrição
-sr

Especifica o local onde o certificado será armazenado. Entre esses valores temos: currentuser ou localmachine.

-ss

Especifica o nome do certificado onde o output será armazenado.

-sky

Especifica o tipo do certificado, que deve ser signature ou exchange.

-pe

Define como será gerada a chave privada, o que permite que a mesma seja incluída no certificado.

-a

Indica qual será o algoritmo usado pelo certificado. Entre os algoritmos temos o md5 (padrão) ou o sha1.

-n

Especifica o nome do certificado, devendo estar em conformidade com o padrão X.500, que é especifcar o nome entre aspas e precedido por CN=.

Observações: Se quiser uma lista completa de todas as opções fornecidas pelo utilitário Makecert.exe, consulte este link. Depois de criado o certificado, se desejar visualizá-lo, vá até a opção Run do Windows e digite: mmc; em seguida, vá até o menu File, Add/Remove Snap-in…, terá um botão chamado Add no fim da janela; ao clicar nele um novo formulário será apresentado e na listagem terá um item chamado Certificates que, ao selecioná-lo, abrirá um wizard onde você deverá, na primeira tela apresentada, selecionar a opção Computer account. Isso fará com que todos os certificados do seu computador sejam exibidos na console de administração. Ao navegar até Personal, Certificates, verá ali o certificado recém criado.

Como o nosso foco será disponibilizar isso através de uma aplicação que irá correr dentro do IIS, é necessário concedermos direitos de leitura a chave privada do certificado em questão para as contas dos worker process do IIS. Para isso, temos que seguir dois passos que são mostrados abaixo:

  • Através do utilitário FindPrivateKey.exe, conseguimos identificar a chave privada, para que seja possível encontrá-la e, em seguida, conceder os direitos a mesma. Se não tiver o Microsoft SDK for .NET Framework 3.0 instalado, então voce pode baixar os exemplos de WCF da Microsoft que, dentro dele, existe um projeto chamado FindPrivateKey.sln, que é justamente o projeto que gera esse utilitário.

  • Depois de encontrada a chave, voce pode conceder os direitos através do utilitário cacls.exe (como é mostrado neste post) ou via Windows Explorer, através da aba Security.

Criação do contrato e configuração da autorização

Neste momento, vamos nos concentrar na criação e configuração do serviço WCF. Mais uma vez, vou considerar como conhecimento prévio o procedimento para a criação de serviço básico de WCF, principalmente no que diz respeito aos atributos que devem ser aplicados aos tipos para torná-lo um serviço WCF. Para o exemplo, vamos criar um contrato padrão, chamado de IServiceContract, que será implementado pelos serviços concretos. Esse contrato (uma Interface) terá dois métodos simples, chamados de BoasVindas e HoraAtual. Essa Interface está exibida abaixo:

using System;
using System.ServiceModel;

namespace DevMinds.ComponentCS
{
    [ServiceContract]
    public interface IServiceContract
    {
        [OperationContract]
        string BoasVindas();

        [OperationContract]
        DateTime DataAtual();
    }
}

Como estamos falando sobre a integração com a segurança do ASP.NET, esse serviço utilizará como hosting o IIS. Sendo assim, temos uma arquivo com extensão *.svc que implementa a Interface definida acima. A única exceção para um exemplo simples, é que os métodos são decorados com o atributo PrincipalPermissionAttribute. Esse atributo, contido dentro do namespace System.Security.Permissions, permite você configurar as informações de segurança, mais especificamente de autorização, utilizando o padrão declarativo e, como podemos notar no exemplo abaixo, cada um dos métodos pode ser invocado por um grupo específico:

using System;
using System.Threading;
using System.Security.Permissions;
using DevMinds.ComponentCS;

public class DefaultService : IServiceContract
{
    [PrincipalPermission(SecurityAction.Demand, Role = "User")]
    public string BoasVindas()
    {
        return string.Format("Ola {0}!",
            Thread.CurrentPrincipal.Identity.Name);
    }

    [PrincipalPermission(SecurityAction.Demand, Role = "Manager")]
    public DateTime DataAtual()
    {
        return DateTime.Now;
    }
}

Um detalhe para a autorização é que se um determinado método é permitido ser invocado por mais de um grupo, você pode adicionar múltiplos atributos PrincipalPermission. Além disso, esse atributo permite a definição de um nome de usuário que tem acesso ao membro. Segurança declarativa força definirmos o nome dos grupos e usuários em hardcode. Se esses valores são dinâmicos, o ideal é estar fazendo isso a partir da segurança imperativa, ou seja, via código. Além deste detalhe, a declaração imperativa permite uma melhor customização, pois podemos ter condicionais para determinar se a segurança será ou não aplicada.

A autorização imperativa ou declarativa não é exclusividade do Windows Communication Foundation ou mesmo da plataforma 2.0 do .NET, mas já existe desde a primeira versão e foi adequado ao sistema de autorização do WCF.

Configuração do serviço

Como já estou assumindo que você tenha os devidos conhecimentos sobre a configuração do Membership e RoleProvider, vou ocultar aqui a explicação deles, mas no final desta seção, mostrarei o Web.Config na íntegra. Basicamente teremos na seção connectionStrings a conexão com a base de dados SQL Server onde estarão as tabelas utilizadas pela infraestrutura do Membership e RoleProvider.

As configurações no serviço que devemos efetuar se resume às seções de bindings e behaviors, as quais iremos analisar individualmente cada um dos elementos e atributos que constituem essa configuração de segurança com integração ao ASP.NET 2.0.

Em um ambiente de internet, as mensagens devem ser transferidas com as credenciais (login e password) informadas pelo usuário. Sendo assim, neste caso utilizaremos o binding WSHttpBinding. Este, por sua vez, representa um binding que pode utilizar HTTP ou HTTPs para transporte, suportando transações distribuídas, confiabilidade e segurança nas mensagens. É bom lembrar que a classe BasicHttpBinding é utilizada para a comunicação com Web Services (ASMX) e não fornece segurança. Se desejarmos ter um canal seguro utilizando este binding, devemos recorrer ao HTTPs. Já o WSHttpBinding possui segurança a nível de mensagem e é isso que veremos a seguir, ou seja, como configurar essa segurança.

A classe WSHttpBinding possui uma propriedade chamada Security que expõe um objeto do tipo WSHttpSecurity. Este objeto possui duas propriedades importantes que utilizaremos na configuração do binding. A primeira delas é a propriedade Mode, que aceita uma das opções fornecidas pelo enumerador SecurityMode. As opções estão descritas na tabela abaixo:

Opção Descrição
Message

Nesta opção, a segurança é fornecida utilizando a segurança do protocolo SOAP.

None

A segurança está desabilitada.

Transport

A segurança será fornecida utilizando HTTPs.

TransportWithMessageCredential

Esta opção permite efetuar um mix das opções anteriores: utilizará um endpoint HTTPs para fornecer autenticação, integridade e confidenciabilidade, enquanto as credenciais do usuário continua sendo enviada através da mensagem SOAP (header) por questões de flexibilidade. Um dos benefícios desta técnica é que todo envelope SOAP é criptografado durante o transporte, incluindo os headers.

Já a segunda propriedade, chamada Message, é onde configuramos o nível de segurança para o transporte da mensagem. Essa propriedade expõe um objeto do tipo NonDualMessageSecurityOverHttp que possui uma propriedade chamada ClientCredentialType. Através desta propriedade especificamos o tipo da credencial do cliente que é utilizada para a autenticação do mesmo quando estamos utilizando o WsHttpBinding binding. Essa propriedade recebe uma das opções contidas no enumerador MessageCredentialType, que estão abaixo descritos:

Opção Descrição
Certificate

Especifica que a autenticação do cliente será efetuada utilizando um certificado.

IssuedToken

Especifica que a autenticação do cliente será efetuada utilizando um issued token.

None

Especifica a autenticação anônima.

UserName

Especifica que a autenticação do cliente será efetuada através de um username.

Windows

Especifica que a autenticação do cliente será efetuada através do Windows.

Como já conhecemos as opções disponíveis para a configuração do binding WSHttpBinding, é necessário neste momento analisarmos os behaviors que também são uma peça fundamental neste processo. Através dos behaviors do serviço, iremos especificar o certificado que criamos acima e, além disso, vamos determinar qual será a forma de autenticação (Membership Provider) e autorização (Role Provider) que o serviço irá utilizar.

Já falando de arquivo de configuração (Web.Config), através do elemento serviceBehaviors vamos customizar a autenticação e autorização. A autenticação (juntamente com o certificado) é realizada dentro do elemento serviceCredentials e a autorização através do elemento serviceAuthorization. O elemento serviceCredentials, responsável pela autenticação, possui dois sub-elementos, a saber: userNameAuthentication e serviceCertificate onde, no primeiro, especificamos a forma da autenticação e o segundo, informações relacionadas ao certificado que será utilizado.

Antes de dar maiores detalhes sobre a configuração da autenticação e autorização, vamos visualizar na íntegra o arquivo Web.Config da aplicação web que hosperará o serviço WCF:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add
      name="SqlServerConnString"
      connectionString="Data Source=.;Initial Catalog=DBWCF;Integrated Security=SSPI;"
      providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <system.serviceModel>
    <services>
      <service name="DefaultService" behaviorConfiguration="ServiceBehavior">
        <endpoint
          name="DevMindsEndpoint"
          contract="DevMinds.ComponentCS.IServiceContract"
          binding="wsHttpBinding"
          bindingConfiguration="ServiceBinding" />
      </service>
    </services>
    <bindings>
      <wsHttpBinding>
        <binding name="ServiceBinding">
          <security mode="Message">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehavior">
          <serviceCredentials>
            <userNameAuthentication
                userNamePasswordValidationMode="MembershipProvider"
                membershipProviderName="SqlMembershipProvider"/>
            <serviceCertificate
                storeLocation="LocalMachine"
                storeName="My"
                x509FindType="FindBySubjectName"
                findValue="IACertificate" />
          </serviceCredentials>
          <serviceAuthorization
              principalPermissionMode="UseAspNetRoles"
              roleProviderName="SqlRoleProvider" />
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
  <system.web>
    <membership
      defaultProvider="SqlMembershipProvider"
      userIsOnlineTimeWindow="15">
      <providers>
        <clear />
        <add
          name="SqlMembershipProvider"
          type="System.Web.Security.SqlMembershipProvider"
          connectionStringName="SqlServerConnString"
          applicationName="WCFApp" />
      </providers>
    </membership>
    <roleManager enabled="true" defaultProvider="SqlRoleProvider" >
      <providers>
        <add
          name ="SqlRoleProvider"
          type="System.Web.Security.SqlRoleProvider"
          connectionStringName="SqlServerConnString"
          applicationName="WCFApp"/>
      </providers>
    </roleManager>
  </system.web>
</configuration>

Como explicar a configuração dos providers está fora do escopo deste artigo, vamos abordar diretamente o elemento userNameAuthentication. Ele possui dois atributos: userNamePasswordValidationMode e membershipProviderName. O primeiro deles determina qual será a forma de validar o username/password. As opções possíveis são fornecidas através do enumerador UserNamePasswordValidationMode. As opções que ele fornece estão descritas na tabela abaixo:

Opção Descrição
Custom

A validação do usuário será baseada em uma forma customizada. Esse modo exige que você crie seu próprio “validador” e, para isso, herde a classe UsernamePasswordValidator e customize os membros necessários.

MembershipProvider

Especifica que a validação da autenticação do usuário será efetuada utilizando a estrutura de Membership Provider do ASP.NET 2.0.

Windows

Os userNames são mapeados para os usuários do Windows.

Como o foco do artigo é a integração de serviços WCF e a estrutura de autenticação do ASP.NET 2.0, o atributo userNamePasswordValidationMode será definido como MembershipProvider, o que nos obriga a definir o atributo membershipProviderName que, por sua vez, receberá uma string contendo o nome do membership provider que irá consistir a validação do usuário. Se reparar, ele recebe o mesmo valor definido no atributo name do elemento membership.

Antes de abordarmos a autorização, é necessário entender como configurar o certificado. Como falamos mais acima e você pode notar no Web.Config, existe um elemento chamado serviceCertificate, que é onde definimos o certificado que o serviço irá utilizar. Esse elemento possui basicamente quatro atributos que utilizamos para determinar o critério de busca pelo mesmo dentro do computador onde o serviço está rodando. Cada um desses atributos estão descritos na tabela abaixo:

Atributo Descrição
FindValue

Recebe uma string contendo o nome do certificado a ser procurado no repositório.

StoreLocation

Especifica o local do repositório onde o certificado está armazenado. Os possíveis valores para este campo são:

  • CurrentUser: especifica que a busca será realizada no repositório de certificados do usuário corrente.
  • LocalMachine: especifica que a busca será realizada no repositório de certificados da máquina local.
StoreName

Especifica o nome (tipo) do repositório. No nosso caso, utilizaremos a opção My, que indica que deve ser efetuada a busca no repositório de certificados pessoais.

X509FindType

Especifica por qual campo o valor informado na propriedade FindValue será procurado. No nosso caso, utilizaremos a opção FindBySubjectName que indica que a busca pelo certificado deverá ser realizada comparando o valor definido na propriedade FindValue com o subject name dos certificados armazenados no local especificado.

Com a autenticação devidamente configurada, chega o momento de analisarmos a autorização. Ainda dentro dos behaviors do serviço, temos um elemento chamado serviceAuthorization, que é utilizado para a configuração da autorização dentro do serviço WCF. Esse elemento possui dois atributos chamados principalPermissionMode e roleProviderName. O primeiro deles determina qual será o modo de autorização utilizado pelo serviço para controlar o acesso aos membros que utilizam a segurança declarativa (atributo PrincipalPermissionAttribute) ou imperativa. As opções possíveis são fornecidas através do enumerador PrincipalPermissionMode. As opções que ele fornece estão descritas na tabela abaixo:

Opção Descrição
Custom

Permite ao desenvolvedor especificar uma forma customizada de autorização, baseando-se em uma classe que implementa a interface IPrincipal, que deve ser vinculada à propriedade CurrentPrincipal.

None

CurrentPrincipal não definida.

UseAspNetRoles

Especifica que a autorização será baseada na Role Provider fornecida pelo ASP.NET 2.0.

UseWindowsGroups

A autorização será baseada nos grupos do Windows.

Como o foco do artigo é a integração de serviços WCF e a estrutura de autorização do ASP.NET 2.0, o atributo principalPermissionMode será definido como UseAspNetRoles, o que nos obriga a definir o atributo roleProviderName que, por sua vez, receberá uma string contendo o nome da role provider que irá consistir a autorização do usuário. Atente-se que ele recebe o mesmo valor definido no atributo name do elemento roleManager.

Observação: Quando habilitamos a segurança baseada em roles no ASP.NET 2.0, utilizando a arquitetura de Role Provider, o ASP.NET cria um objeto do tipo RolePrincipal e define o mesmo na propriedade CurrentPrincipal da classe Thread que está cuidando da execução do pedido. Quando o método IsInRole da classe RolePrincipal for invocado, ela ainda não possui conhecimentos das roles do usuário e invoca o método GetRolesForUser do provider, recuperando as roles do repositório para o usuário em questão e, em seguida, cria um cache internamente que evitará ir até o repositório toda vez que este método for chamado. Isso tem um comportamento ligeiramente diferente quando configuramos o serviço WCF para utilizar a arquitetura de roles do ASP.NET 2.0, pois o WCF utilizará uma classe customizada, chamada RoleProviderPrincipal, que está contida dentro do namespace System.ServiceModel.Security e implementa a Interface IPrincipal. Durante a execução de um serviço WCF, uma instância dessa classe é anexada à propriedade CurrentPrincipal da classe Thread. O grande problema com ela é que, ao invocar o método IsInRole a chamada é encaminhada para o método IsUserInRole do provider especificado. Isso quer dizer que, no caso do SqlRoleProvider, não será efetuado o cache das roles do usuário, aumentando assim o número de round-trips no banco de dados.

Com essa configuração relizada e o serviço já disponível no servidor (incluindo a instalação do certificado), o mesmo já pode ser invocado pelos clientes interessados em consumí-lo. Superficialmente, o que acontece nos bastidores durante a chamada ao serviço WCF, é que o cliente utilizará a chave pública (fornecida pelo serviço) para criptografar todas as mensagens que enviará ao serviço. Quando o serviço recebe essa mensagem, ele utiliza a chave privada para decriptografar a mensagem e, feito isso, o serviço WCF lê as credenciais do usuário e o autentica, permitindo assim, o acesso ao serviço.

Configuração do cliente

Assim como o serviço, também é necessário configurarmos o binding e o behavior do serviço WCF do lado do cliente e, além disso, necessitamos ter a chave pública do certificado exposta pelo serviço.

Para suportar a autenticação e proteção de mensagens mútuas, os serviços devem fornecer as credenciais para o chamador. Quando utilizamos a segurança a nível de transporte, as credenciais do serviço são negociadas através do protocolo de transporte. Quando utilizamos a segurança baseada em mensagens, podemos também especificar as credenciais do Windows ou um certificado, através do elemento serviceCredentials, como já vimos acima. Esse processo, chamado de negociação, pode ser realizado de forma out-of-band ou através de um handshake inicial.

Se desejar, você pode desabilitar a negociação das credenciais definindo para False o atributo negotiateServiceCredential do elemento message.

Para os tipos de credenciais diferentes de Windows, de alguma forma é necessário que o cliente tenha acesso à chave pública do certificado para que ele possa criptografar as requisições para o serviço. Isso é gerado automaticamente quando você adicionar uma referência para um serviço através do Visual Studio .NET 2005 (ou através do utilitário svcutil.exe) que, nas configurações do arquivo App.Config já está codificada uma versão pública do certificado, que é informada através do atributo encodedValue do elemento certificate que, por sua vez, está contido dentro da seção identity.

Depois da configuração do certificado, vamos falar sobre o binding que irá refletir exatamente a mesma configuração que fizemos no serviço do lado do servidor, ou seja, definiremos o atributo mode do elemento security para Message, e o atributo clientCredentialType do elemento message é definido também como UserName, ficando assim exatamente como a configuração estipulada no serviço.

Finalmente, temos a seção dos behaviors. Essa seção fornece um elemento chamado serviceCertificate. Esse elemento especifica o certificado que será utilizado pelo cliente. Esse elemento tem um sub-elemento chamado authentication que, por sua vez, possui um atributo chamado certificateValidationMode, o qual é extremamente importante na validação do certificado. Esse atributo recebe uma das opções fornecidas pelo enumerador X509CertificateValidationMode. Essas opções estão descritas na tabela abaixo:

Opção Descrição
ChainTrust

Esta opção instrui o WCF a confiar no certificado somente se ele foi emitido através de um orgão como a VeriSign.

Custom

O certificado é validado através de um validador customizado. Esse validador deve ser customizado pelo desenvolvedor, herdando o seu validador a partir da classe X509CertificateValidator.

None

Validação do certificado está desabilitada.

PeerOrChainTrust

Esta opção permite o WCF confiar em um certificado somente se ele foi emitido através de um orgão como a VeriSign ou se ele estiver dentro do repositório Trusted People do cliente.

PeerTrust

O certificado é validado se ele estiver dentro do repositório Trusted People do cliente.

Com isso, o arquivo de configuração do cliente, App.Config, fica da seguinte forma:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint
        name="DevMindsService"
        address="http://localhost:2969/DevMinds.WebHostingCS/DefaultService.svc"
        behaviorConfiguration="ClientBehavior"
        binding="wsHttpBinding"
        bindingConfiguration="ClientBinding"
        contract="DevMinds.ClientUICS.localhost.IServiceContract">
        <identity>
          <certificate
            encodedValue="AwAAAAEAAAAUAAAAUJNV9NaKKu1ufLDJSReysIhSHokgAAAAAQAAALkBAAAwgg
                          G1MIIBY6ADAgECAhD8doIP5AdsgEtkMqF7eimCMAkGBSsOAwIdBQAwFjEUMBIG
                          A1UEAxMLUm9vdCBBZ2VuY3kwHhcNMDcwNDAzMTMxNjM3WhcNMzkxMjMxMjM1OT
                          U5WjAYMRYwFAYDVQQDEw1JQUNlcnRpZmljYXRlMIGfMA0GCSqGSIb3DQEBAQUA
                          A4GNADCBiQKBgQC/+fgp/kuwIVmhpJW2XFDdFQKT0hDRhbdsIvV8ikrUANCuii
                          ufPvo10FqMrVPqeowy+c2XxYpQ6lGeMx4QWVeZC9LdGCzli1UNKdarT6wBiBsK
                          qAxmb6lJkKnkL4HycepYckbT2H65oIDDnfFNv1TYUP1/gPHoz6bsIFCa4eXCMw
                          IDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEUMBIG
                          A1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwCQYFKw4DAh0FAA
                          NBAGoc8S3JfsoQPRqwCIO0WYopE8vJ92IYLkxQ5k68BuN1mLUftbVoEv/TO/Y5
                          7ToFWzTsDMT5BfG37G1Y0P5Iq+Q=" />
        </identity>
      </endpoint>
    </client>
    <bindings>
      <wsHttpBinding>
        <binding name="ClientBinding">
          <security mode="Message">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="ClientBehavior">
          <clientCredentials>
            <serviceCertificate>
              <authentication certificateValidationMode="PeerTrust" />
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Invocando os métodos

Como as configurações já estão definidas tanto do lado do servidor quanto do cliente, chega o momento em que devemos invocar os métodos que o serviço disponibiliza.

Basicamente, a chamada para os métodos não mudando absolutamente em nada. A única diferença é que devemos, antes de invocar o(s) método(s), informar o nome de usuário e senha para o proxy, que enviará essas credenciais para o serviço quando o método for invocado. O proxy expõe uma propriedade chamada ClientCredentials do tipo ClientCredentials. Essa classe possui uma propriedade chamada UserName do tipo UserNamePasswordClientCredential.

Essa classe habilita o usuário a configurar o userName e o password que o WCF deve utilizar quando estiver efetuando a autenticação. Essa simples classe possui duas propriedades que recebem uma string. Essas propriedades, auto-explicativas, são chamadas de Password e UserName. O exemplo (Windows Application) abaixo ilustra como devemos configurar essas propriedades, informando o userName e password:

using (localhost.ServiceContractClient c = new localhost.ServiceContractClient())
{
    c.ClientCredentials.UserName.UserName = "Claudia";
    c.ClientCredentials.UserName.Password = "123456";
    MessageBox.Show(c.DataAtual().ToString());
    MessageBox.Show(c.BoasVindas());
}

Recapitulando o código de implementação do serviço, o método BoasVindas somente poderá ser executado por usuários que estão contidos dentro da role User. Já o método DataAtual somente pode ser executado por usuários que estão contidos dentro da role Manager.

Como podemos reparar, passamos como userName o nome Claudia, que executa os dois métodos com sucesso pois este usuário está contido dentro das duas roles. Se alterarmos o userName para Juliano, que está contido somente dentro da role User, ao tentarmos executar o método DataAtual, que é permitido somente a usuários que estão contidos dentro da role Manager, uma exceção será atirada informando que o acesso foi negado.

Conclusão: O WCF possui uma grande variedade de formas de autenticação e autorização para os serviços. Neste artigo abordamos uma das formas de segurança que o WCF expõe, que é justamente a integração da autenticação e autorização com toda a infraestrutura que o ASP.NET 2.0 já fornece para os desenvolvedores de aplicações web e, como é o intuíto do WCF, toda a configuração para integrar com o ASP.NET 2.0, bem como a maioria da segurança do WCF, baseado em plug-and-play.

Windows RSS Platform

Navegando pela internet acabei achando este artigo, que introduz o Windows RSS Plataform. Essa plataforma permite a interação com os famosos feeds, geralmente utilizado em blogs, para o consumo em aplicações construídas por nós, sendo ela .NET ou não.

Quando instalamos o Internet Explorer 7.0, um novo componente COM, chamado Microsoft Feeds, é criado e, consequentemente, pode ser utilizado dentro de uma aplicação .NET através de Interop. Para exemplificar a utilização do mesmo, o artigo citado ilustra a sua utilização.

Para aqueles que desejam se aprofundar, este link do MSDN possui informações a nível dos tipos (classes, interfaces e enumeradores) disponíveis dentro deste componente.

WCF – Hosting

Como já sabemos, WCF – Windows Communication Foundation – é parte integrante do .NET Framework 3.0. Ele fornece um conjunto de classes para a construção e hospedagem de serviços baseando-se na arquitetura SOA (Service Oriented Architecture), podendo expor tais serviços para serem acessados através dos mais diversos tipos de protocolos, como por exemplo: HTTP, TCP, IPC e MSMQ. Atualmente existem três tipos de hosts para serviços construídos em WCF: self-hosting (através da classe ServiceHost), IIS (Internet Information Services) e WAS (Windows Activation Services – Windows Vista) e, é exatamente isso que este artigo abordará, ou seja, como configurar cada um desses hosts para expor os serviços construídos em WCF.

Como o foco do artigo é exclusivamente a configuração do host, vou considerar como conhecimento prévio o procedimento para a criação de serviço básico de WCF, principalmente no que diz respeito aos atributos que devem ser aplicados ao serviço para torná-lo um serviço WCF. Para o exemplo, vamos criar um contrato padrão, chamado de IServiceContract, que será implementado pelos serviços concretos. Esse contrato (uma Interface) terá dois métodos simples, chamados de BoasVindas e HoraAtual. Essa Interface está exibida abaixo:

using System;
using System.ServiceModel;

namespace DevMinds.ComponentCS
{
    [ServiceContract]
    public interface IServiceContract
    {
        [OperationContract]
        string BoasVindas();

        [OperationContract]
        DateTime DataAtual();
    }
}

Self-Hosting

O self-hosting trata de uma aplicação, seja ela Console, Windows Forms, Windows Service, etc., que expõe um ou vários serviços para serem consumidos por seus clientes. Para que seja possível expor o serviço programaticamente, ou seja, sem utilizar o IIS ou o WAS, primeiramente é necessário fazer referência ao arquivo System.ServiceModel.dll. Esse Assembly contém todas as classes necessárias para trabalhar com Windows Communication Foundation – WCF – e fornece também a classe ServiceHost. Essa classe fornece toda a infraestrutura necessária para expor serviços WCF.

Um dos overloads do construtor da classe ServiceHost recebe um objeto do tipo Type, onde devemos apontar qual será o tipo que o host irá expor. Em seguida, é necessário definir o endpoint que indicará em que local e com qual protocolo o serviço será exposto e, para atender essa necessidade, a classe ServiceHost fornece um método chamado AddServiceEndpoint para criar quantos endpoints forem necessários para o serviço. A classe ServiceHost ainda fornece alguns eventos que podem ser úteis para fins de monitoramento. Esses eventos estão descritos na tabela abaixo:

Evento Descrição
Closed

Ocorre quando a comunicação com o objeto é fechada.

Closing

Ocorre quando a comunicação com o objeto estiver sendo fechada.

Faulted

Ocorre quando uma falha acontece.

Opened

Ocorre quando a comunicação com o objeto é aberta.

Opening

Ocorre quando a comunicação com o objeto estiver sendo aberta.

UnknownMessageReceived

Ocorre quando uma mensagem desconhecida é recebida.

Para que os clientes possam extrair as informações (metadados) necessárias a respeito do serviço, precisamos expor a descrição do mesmo (que descreve todas as funcionalidades fornecidas pelo serviço, endpoints e seus comportamentos) para que os desenvolvedores possam referenciá-lo nas aplicações clientes que irão consumí-lo. Você pode expor essas informações através do protocolo HTTP (via método Get) ou utilizar um endpoint dedicado. O Windows Communication Foundation disponibiliza os metadados automaticamente para o serviço através de HTTP-GET e, mais adiante, veremos como disponibilizar o contrato através de outros endpoints, com protocolos diferentes. Tudo o que você precisa fazer é habilitar isso, adicionando explicitamente um service behavior.

Resumindo, o MEX (metadata exchange endpoint) é extremamente necessário, já que o cliente necessita importar para ter localmente a representação do serviço e, conseqüentemente, invocá-lo. Para configurarmos um serviço a ser exposto, podemos fazer via código (Visual C# ou Visual Basic .NET) ou declarativamente, através do arquivo *.Config da aplicação. Como discutimos acima, um serviço WCF fornece, por padrão, a descrição do mesmo através do protocolo HTTP-GET; mas para isso, precisamos apenas habilitá-lo, através do atributo httpGetEnabled do elemento serviceMetadata, se estivermos falando da configuração via arquivo *.Config e se quisermos expor a descrição via HTTP-GET.

Ainda há a possibilidade de expor a descrição do serviço através de um endpoint customizado, podendo acessá-lo através de um outro protocolo, como por exemplo o TCP. Este cenário exige a criação de um endpoint simples, como já fazemos normalmente para um serviço qualquer, mas neste caso, o tipo de contrato a ser exposto será baseado na Interface IMetadataExchange, que está definida dentro do namespace System.ServiceModel.Description, o qual expõe métodos que retornam metadados referentes ao serviço.

Os endpoints (que expõem o serviço e os metadados) podem ser configurados declarativamente ou programaticamente. Iremos analisar essas duas formas. Para fins de exemplo, utilizaremos um projeto do tipo Console Application mas, um pouco adiante, falaremos sobre as diversas aplicações que podem servir como host de serviço WCF. O código abaixo exemplifica como criar via código o endpoint necessário para expor o serviço e um outro endpoint para o MEX, ambos utilizando protocolo TCP:

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;

using DevMinds.ComponentCS;

static void Main(string[] args)
{
    using (ServiceHost host = new ServiceHost(typeof(DefaultService),
        new Uri("net.tcp://localhost:9000/")))
    {
        host.Description.Behaviors.Add(new ServiceMetadataBehavior());

        host.AddServiceEndpoint(
            typeof(IMetadataExchange),
            MetadataExchangeBindings.CreateMexTcpBinding(),
            "MEX/");

        host.AddServiceEndpoint(
            typeof(IServiceContract),
            new NetTcpBinding(),
            "DefaultService/");

        host.Open();
        Console.ReadLine();
    }
}

Como podemos notar, criamos a instância da classe ServiceHost, informando em seu construtor o tipo que a mesma irá servir e em qual endereço irá disponibilizá-la. Em seguida, adicionamos uma instância da classe ServiceMetadataBehavior dentro de uma coleção chamada Behaviors relacionada ao host recém criado; este passo é necessário porque o host valida o tipo de contrato especificado nos endpoints, ou seja, o contrato do endpoint deve ser compatível com o contrato declarado na criação do ServiceHost. No caso acima, a classe que criamos, DefaultService, implementa a Interface IServiceContract, mas não implementa a Interface IMetadataExchange. Internamente quando o host detecta que os tipos não são compatíveis, ele analisa os behaviors que foram registrados; se ele encontrar uma instância da classe ServiceMetadataBehavior, somente com ela presente será possível adicionar o endpoint de metadados, que já está logo em seguida. Neste momento, entra em cena uma classe estática chamada MetadataExchangeBindings. Essa classe fornece membros estáticos que auxiliam na criação de bindings exclusivos para cada um dos protocolos, que irá depender da forma de comunicação que deseja ter com os clientes para saber qual desses métodos utilizar.

A mesma configuração acima dentro do arquivo *.Config fica da seguinte forma (note que no atributo contract do elemento endpoint é necessário especificar a interface do contrato do serviço, incluindo os namespaces até ela):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="DevMinds.ComponentVB.DefaultService" behaviorConfiguration="MEX">
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:9000/"/>
          </baseAddresses>
        </host>
        <endpoint
          contract="IMetadataExchange"          
          binding="mexTcpBinding"
          address="MEX/" />
        <endpoint
          contract="DevMinds.ComponentVB.IServiceContract"
          binding="netTcpBinding"
          address="DefaultService/" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MEX">
          <serviceMetadata />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Nota: É importante dizer que a configuração via arquivo *.Config não dispensa a criação do objeto ServiceHost no código e sua abertura através do método Open. A única diferença neste caso, é que não há a necessidade de informar o baseAddress, behaviors e os endpoints.

Com o host devidamente configurado, independente se foi através de código declarativo (*.Config) ou via código, basta inicializar a aplicação e, através do endereço que foi especificado para criar o serviço, você pode adicionar a referencia na aplicação cliente e, em seguida, já conseguirá consumir o serviço.

Como falamos acima, o self-hosting permite utilizarmos uma aplicação .NET para disponibilizá-lo. Existem três tipos de aplicações que podem atender essa necessidade: Console Application (onde baseamos o nosso teste), Windows Forms e Windows Service. A Console Application é muito útil quando estamos efetuando testes ou mesmo em tempo de desenvolvimento e jamais é indicada para o ambiente de produção, já que necessita de um usuário logado na máquina onde o executável reside e, ainda podemos acidentalmente fechar o host, o que evitaria que requisições subseqüentes sejam processadas.

A outra opção é a utilização de criar o host em uma aplicação Windows Forms. Ela é um pouco melhor em relação a Console Application, mas ainda exige que um usuário acesse o sistema para que possa inicializar o host. Finalmente, temos o Windows Service. Este sim é um dos mais apropriados tipos de aplicações para servir como host de uma serviço WCF, pois não necessita que o usuário esteja logado para que funcione e ainda pode ser configurado para ser inicializado automaticamente quando o computador for ligado. Independente disso, o host já deve estar no ar quando a requisição chegar, o que já não acontece com o IIS, pois ele ativa o objeto assim que a requisição chega ao servidor. Analisaremos o host através do IIS na próxima seção.

IIS – Internet Information Services

Até o lançamento do WCF tínhamos (e ainda temos) à disposição dentro da plataforma .NET, a possibilidade de construirmos os famosos Web Services (ASMX). Os Web Services foram lançados desde a versão 1.0 do .NET Framework e revolucionou a forma de como expor dados e componentes para consumo. Eles, por sua vez, estão fortemente ligados à arquitetura do ASP.NET, onde a dependência do protocolo HTTP e algumas de suas funcionalidades específicas são uma das “piores” partes, já que quebra alguns dos conceitos que a arquitetura SOA impõe.

Os serviços WCF também podem utilizar o IIS como hosting, de forma bem semelhante aos Web Services (ASMX), tirando alguns proveitos do mesmo; um dos principais é a ativação do processo, que é feita quando uma requisição chega até o servidor. Mas, como nem tudo é perfeito, o IIS limita o tipo de transporte, obrigando-nos a utilizar somente o protocolo HTTP.

Para que seja possível fazer o host de um serviço dentro do IIS, é necessário que você tenha um arquivo físico com a extensão *.svc. Esse tipo de arquivo está associado com um serviço do WCF e, quando a requisição para um recurso deste tipo chega no servidor, ele é tratado através de um módulo chamado HttpModule em conjunto com um handler chamado HttpHandler, que estão contidos dentro do namespace System.ServiceModel.Activation (isso pode ser confirmado através do arquivo Web.Config que está dentro do seguinte endereço: %windir%Microsoft.NETFrameworkv2.0.50727CONFIG). A forma com que estes objetos trabalham irá depender do modo de compatibilidade com o ASP.NET que pode estar ou não estar habilitado. Analisaremos os dois modos existentes mais adiante, ainda neste artigo.

Os arquivo com extensão *.svc tem uma diretiva ServiceHost, onde informamos a linguagem que estaremos escrevendo o código, o nome do serviço e o caminho até o arquivo de CodeBehind do serviço, que reside dentro da pasta especial App_Code. Isso é bem semelhante à estrutura de uma página ASP.NET. O código abaixo mostra a estrutura, de apenas uma linha, de um arquivo *.svc:

[ C# ]
<%@ ServiceHost Language="C#" 
                   Debug="true" 
                   Service="DefaultService"
                   CodeBehind="~/App_Code/DefaultService.cs" %>

Dentro do arquivo de CodeBehind criamos uma classe e implementamos a Interface IServiceContract (já comentada mais acima). Depois de realizada a devida implementação devida realizada, definindo efetivamente o que os métodos irão fazer, é necessário criarmos o endpoint no arquivo Web.Config para que o IIS consiga identificar e expor o serviço. A configuração é bem semelhante ao arquivo App.Config que vimos acima, que configura o serviço em um self-hosting. Ligeiras mudanças são realizadas, conforme é mostrado abaixo:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="DefaultService" behaviorConfiguration="returnFaults">
        <endpoint 
          contract="DevMinds.ComponentCS.IServiceContract" 
          binding="wsHttpBinding"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="returnFaults">
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

É importante analisar que na criação do endpoint não é necessário o atributo address, pois o endereço (base address) será determinado a partir do endereço do arquivo *.svc. Outro detalhe importante é com relação ao atributo httpGetEnabled do elemento serviceMetadata que, quando estiver definido como True, é responsável por habilitar que o contrato do serviço seja publicado e, conseqüentemente, acessado pelos clientes. Finalmente, o atributo includeExceptionDetailInFaults, que é utilizado para propósitos de depuração pois, quando é definido como True, propaga qualquer exceção atirada pelo serviço para os clientes, facilitando a depuração quando estamos consumindo o serviço em um ambiente de testes. Claro que essa informação somente deve ser disponibilizada quando o serviço ainda está em processo de desenvolvimento, pois se essas informações chegarem aos clientes finais, você pode estar abrindo brechas para que ele possa explorar as informações de erro, inclusive o stack trace.

Ao invocar o serviço no browser, já conseguimos ter acesso ao mesmo, inclusive ao seu contrato. A imagem abaixo ilustra o serviço sendo apresentado no browser:

Figura 1 – Output do serviço sendo invocado no browser.

Neste momento o serviço já está pronto para ser consumido pelos clientes e, como podemos notar na imagem acima, ela mesma exibe um exemplo de como gerar o proxy no cliente, através do utilitário svcutil.exe ou ainda, se estiver utilizando o Visual Studio .NET 2005 com o .NET Framework 3.0 devidamente instalado, pode ir diretamente no projeto onde deseja consumir o serviço, clicar com o botão direito do mouse em cima do mesmo e, em seguida, clicar na opção “Add Service Reference…”, informando o endereço HTTP acima, que automaticamente o proxy será gerado e adicionado à aplicação.

Podemos tranquilamente criar um diretório virtual dentro do IIS para hospedar os serviços WCF. Porém há cenários onde os serviços WCF estão dentro do IIS, mas contidos em uma aplicação ASP.NET ou até mesmo em uma aplicação do tipo Web Services (ASMX). Em todas as ocasiões, muitas vezes é necessário fazer o uso de algumas funcionalidades específicas do protocolo HTTP para atender uma determinada necessidade, como por exemplo, a utilização de variáveis de aplicação (Application), sessão (Session) ou até mesmo informações contidas no contexto HTTP (HttpContext). O WCF, em conjunto com o runtime do ASP.NET, fornece dois modos de execução de um pedido para um serviço, que tratam de forma bem diferente a requisição ao *.svc. Essas formas são: Mixed Transports Mode ou ASP.NET Compatibility Mode, as quais falaremos a seguir.

Na primeira delas, Mixed Transports Mode (modo default), a requisição do serviço não é executada pelo ASP.NET pipeline. Isso permite definir endpoints com diferentes formas de transportes. Uma das características mais importantes deste modo é que ele não permite que serviços WCF façam uso de funcionalidades específicas do ASP.NET, mais precisamente do protocolo HTTP. Entre as funcionalidades desabilitadas em virtude de ter esse modo habilitado temos:

Funcionalidade Desabilitada Descrição
HttpContext.Current

Em uma aplicação tradicional Web, a classe HttpContext encapsula informações específicas a respeito da requisição HTTP, que estão acessíveis através da propriedade estática Current.

Em um serviço WCF, essa propriedade sempre retornará nula, mas existe uma classe chamada OperationContext que, através de uma propriedade estática, também chamada Current, retorna informações a respeito conteúdo atual da requisição.

File e UrlAuthorization

A autorização, que é efetuada a partir da seção authorization no arquivo Web.Config, está também desabilitada. Para autorização, você precisará implementar isso nas mensagens que são enviadas/recebidas pelos serviços WCF.

Impersonation

A capacidade de impersonificação que o ASP.NET fornece através do elemento identity passa a ficar desabilitada neste modo.

Session State

As variáveis de sessões não são suportadas neste modo (lembrem-se que isso difere bastante as Sessões do WCF, que serão abordados em um futuro artigo).

Outras

Outras funcionalidades também estão desabilitadas, como é o exemplo da globalização (acessada através do elemento globalization). Além disso, outras funcionalidades que dependem da classe HttpContext, também podem não funcionar corretamente, como é o caso da seção AppSettings.

Já o ASP.NET Compatibility Mode trata de um modo que habilita todas as restrições impostas pelo modo anterior, mas impõe um grande ponto negativo, ou seja, o serviço fica completamente dependente do protocolo HTTP e, sendo assim, quando o serviço estiver sendo ativado, os endpoints serão verificados para assegurar que protocolos não-HTTP estão habilitados.

Existe um elemento no arquivo Web.Config que permite-nos declarativamente habilitar ou desabilitar esse modo. Trata-se do atributo aspNetCompatibilityEnabled do elemento serviceHostingEnvironment. Quando definido como False (padrão), o modo utilizado é o Mixed Transports Mode e, quando True, o modo ASP.NET Compatibility Mode está habilitado. Mas, para habilitar o modo ASP.NET Compatibility Mode, ainda é preciso de uma configuração adicional, mas desta vez diretamente na classe que representa e implementa o contrato do serviço, que é decorar a classe com o atributo AspNetCompatibilityRequirements, que está contido dentro do namespace System.ServiceModel.Activation e configurá-lo de acordo com a necessidade. Esse atributo recebe, através da propriedade RequirementsMode, um dos três valores contidos no enumerador AspNetCompatibilityRequirementsMode, os quais são explicados abaixo:

Opção Descrição
Allowed

Esta opção indica que o serviço pode rodar no modo ASP.NET Compatibility. Mas, internamente, deverá se comportar bem quando esse recurso não estiver habilitado.

NotAllowed

Não permite que o serviço rode em modo ASP.NET Compatibility. Essa opção é utilizada em cenário onde o serviço não é construído exclusivamente para HTTP.

Required

Indica que o serviço exige o modo ASP.NET Compatibility para rodar e, neste caso, poderá fazer o uso das características acima sem nenhuma preocupação, já que essa opção garantirá que o serviço irá rodar em um ambiente onde as características específicas de HTTP estarão habilitadas.

Os trechos de código abaixo, com algumas partes suprimidas por questões de espaço, ilustram a configuração na classe do serviço e no arquivo Web.Config para habilitar o ASP.NET Compatibility Mode:

using System.ServiceModel.Activation;

[AspNetCompatibilityRequirements(
    RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
public class DefaultService : IServiceContract
{
    //Restante da implementação
}

 

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    <services>
      <service name="DefaultService">
        <!-- Endpoints -->
      </service>
    </services>
    <!-- Outras Configurações -->
  </system.serviceModel>
</configuration>

Um pouco acima falamos sobre o módulo HttpModule e o handler HttpHandler, que fazem parte da execução do processamento de uma requisição para um serviço WCF. A forma de trabalho irá depender exclusivamente do modo que está habilitado. Se o modo Mixed Transports estiver habilitado, o módulo HttpHandler irá interceptar a requisição logo após o evento PostAuthenticateRequest e, neste momento, o objeto HttpContext.Current é definido como nulo. Agora, quando o modo ASP.NET Compatibility estiver habilitado, o módulo HttpModule será um simples filtro, sem fazer nada de mais importante. Depois da execução do módulo, o handler é finalmente executado, para efetivamente fazer o trabalho do serviço WCF.

WAS – Windows Activation Services

O Windows Vista fornece um novo recurso chamado WAS – Windows Activation Services. Trata-se de um processo que é instalado dentro do IIS 7, que desacopla a arquitetura de ativação do IIS, permitindo assim o transporte não somente via HTTP, mas também via named pipes, TCP e MSMQ (message queue).

Um outro ponto importante é que, além de suportar múltiplos protocolos, fornece as funcionalidades dadas pelo IIS, como por exemplo o health monitoring, reciclagem de processo e, conseqüentemente, ferramentas de gerenciamento para tais configurações.

Quando o listener de um determinado protocolo recebe a requisição, o WAS verifica a existência do work process para serví-lo e, antes de processar a requisição, o handler assegura que uma instância da classe ServiceHost tenha sido criada. Assim como vimos no IIS, felizmente o WAS utiliza também o modelo de ativação baseado na mensagem, o que aumenta a escalabilidade para as requisições em qualquer protocolo.

O WAS, por padrão, não vêm instalado por padrão. É necessário fazer alguns ajustes para habilitá-lo e, conseqüentemente, disponibilizar serviços WCF para serem consumidos nos mais diversos protocolos. Deixarei a configuração e utilização do WAS dentro do IIS 7 (com Windows Vista) para um futuro artigo, dedicando-o para explorar completamente esse novo recurso.

Conclusão: Como podemos notar no decorrer deste artigo, todo o host basicamente deverá ser um processo executável, que terá um AppDomain onde o serviço será carregado. Agora, com todas essas alternativas, você deve analisar e ser capaz de escolher qual das soluções é mais viável à sua necessidade, sempre pesando os prós e contras de cada um dos tipos de host.

Url dinâmica em Web Services

Quando fazemos a Web Reference no projeto, uma classe proxy herdando de SoapHttpClientProtocol é criada. Esta classe nos fornece uma propriedade chamada URL, que armazena a URL do Web Service que o cliente está invocando. No momento desta referência, o proxy gerado define a URL baseando-se na URL de desenvolvimento (qual informamos), que provavelmente trata-se de um endereço de localhost.

É através desta propriedade que alteramos em tempo de execução no cliente a URL do serviço, desde que, esta nova URL (Web Service) implemente a mesma descrição/serviço que foi anteriormente gerado pela classe proxy. Para ilustrar isso, criamos um Web Service com um WebMethod e o referenciamos na aplicação cliente. Iremos definir a URL do Web Service no arquivo de configuração da aplicação (App.Config) para que, quando a URL mudar, não precisar recompilar a aplicação:

using System.Configuration;

WS.Consultas ws = new WS.Consultas();
ws.Url = ConfigurationSettings.AppSettings["WSURL"];
MessageBox.Show(ws.MensagemBoasVindas(Me.txtNome.Text));

Como vemos no código acima, depois que instanciamos o Web Service, recuperamos a URL do arquivo de configuração através da propriedade AppSettings e definimos na propriedade URL. Em seguida apenas chamamos o WebMethod da forma como já conhecemos. O código abaixo mostra a chave e seu valor (URL) definida no arquivo de configuração:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="WSURL" value="http://localhost/WSNovo/Consultas.asmx" />
    </appSettings>
</configuration>

Mas há ainda a possibilidade de colocarmos isso no construtor da classe Proxy para não ficarmos definindo esse parâmetro a cada instância do WebService. No Web Service temos uma propriedade chamada URLBehavior que, ao definí-la como Dynamic, automaticamente é criado uma chave dentro do arquivo App.Config contendo a URL do Web Service e, o código do construtor da classe proxy que herda de SoapHttpClientProtocol é alterado para buscar a URL Web Service dentro do arquivo de configuração da aplicação. A imagem abaixo mostra a janela de propriedades do Web Service, onde devemos definir a URL Web Service como dinâmica:

Figura 1 – Janela de propriedades do Web Service.

Depois desta alteração, o construtor da classe proxy fica:

public Consultas() {
    string urlSetting = System.Configuration.ConfigurationSettings.AppSettings["WSURL"];
    if ((urlSetting != null)) {
        this.Url = string.Concat(urlSetting, "");
    }
    else {
        this.Url = "http://localhost/WS/Service1.asmx";
    }
}

Acessando uma URL segura (HTTPS) através de System.Net

Hoje tive uma experiencia interessante, ou seja, um Windows Service que criei para automatizar alguns processos aqui na empresa utiliza as classes de HttpWebRequest e HttpWebResponse para enviar e recuperar dados de um serviço prestado por terceiros, que corre em cima de HTTPS, depois de migrado para um servidor Windows Server 2003, ao executá-lo e fazer uma requisição a este link seguro, o seguinte erro era retornado:

An unhandled exception of type ‘System.Net.WebException’ occurred in system.dll
Additional information: The underlying connection was closed: Could not establish trust relationship with remote server.

Fui então fazer alguns testes tentando acessar este mesmo link através do browser, e resultou com sucesso. Pude reparar que ao digitá-lo no browser, uma janela é aberta, perguntando ao usuário se ele quer ou não confiar no certificado digital que é fornecido pelo servidor que estamos tentando acessar. Com certeza optei por sim, e o retorno foi imediato. Mas a questão estava aí, ou seja, se a requisição é feita por um processo automático (Windows Service), que clicaria no botão? 😛 Nesse caso, criamos uma classe que implementa a interface ICertificatePolicy para gerir e autorizar que o certificado seja aceito. O código para isso é:

     Imports System.Net
     Public Class TrustAllCertificatePolicy

          Implements ICertificatePolicy

          Public Function CheckValidationResult(ByVal srvPoint As System.Net.ServicePoint, ByVal certificate As System.Security.Cryptography.X509Certificates.X509Certificate, ByVal request As System.Net.WebRequest, ByVal certificateProblem As Integer) As Boolean Implements System.Net.ICertificatePolicy.CheckValidationResult
               Return True
          End Function

     End Class

No código acima, através do método CheckValidationResult retornamos True para sempre aceitar e confiar neste certificado. Depois disso, antes de efetuar a requisição devemos definí-la na propriedade CertificatePolicy, como é exibido abaixo:

     System.Net.ServicePointManager.CertificatePolicy = New TrustAllCertificatePolicy

Para conseguir a encontrar essa solução, li o artigo do Jan Tielen, e pode ser visto aqui. Lembrando que isso também é valido para Web Services que rodam em ambiente seguro, como ele explica.

Middleware

Acabo de ler um artigo interessante chamado “Escolhendo entre WebServices, Enterprise Services e Remoting”, onde o autor, Otavio Coelho, mostra os prós e contras de cada uma dessas tecnologias para serem ou não utilizadas em uma middleware.

Service is running

Há aproximadamente dois meses iniciei um projeto onde tive que construir um Windows Service (ou WinService). Pois bem, a finalidade do mesmo é resgatar os registros de uma determinada Base de Dados e enviar para um orgão chamado SERASA.

Este orgão tem a finalidade de fornecer informações sobre créditos e restrições financeiras, entre outros documentos e serviços, relacionados à pessoas físicas ou jurídicas (Empresas). Eles fornecem várias formas de resgatar do cliente e devolver para o mesmo tais informações. Alguns dos meios são Sockets, HTTPs e algo que não estou recordando agora (uma solução da IBM).

Como a idéia era criar tudo em .NET, fomos logo lá entender o problema. Ao cadastrar os Clientes na Base de Dados de um sistema interno (desenvolvido em Delphi), o serviço deverá correr sem nenhuma intervenção humana (claro, é esta a finalidade de um WinService) e automaticamente gerar a String de envio e receber o valor de retorno do SERASA. Vale lembrar que muitas vezes, o retorno não é imediato, tendo assim que ficar de tempo em tempo enviando uma String para checar se o lote que foi enviado já foi ou não processado.

Para que o processo corresse corretamente, tivemos que nos atentar à algumas condições, pois existiam seis tipos de documentos a serem enviados e recebidos. O processo de resgate dos dados da Base de Dados, a geração da String de envio, o recebimento e análise do retorno e a persistência na DB são iguais para todos, ou seja, os passos a serem executados devem ser exatamente os mesmos. A solução neste caso, foi criar uma classe Base que implementa um Template Method e dentro dele define os passos do algoritmo, deixando as subclasses (as classes derivadas) implementarem os métodos como eles devem ser, ou seja, é exclusivo para cada subclasse, pois a geração da String de envio por exemplo é diferente entre os tipos de documentos a serem enviados.

Bem, depois do envio e recebimento OK, vem a parte da persistência dos dados retornados pelo SERASA na Base de Dados. Neste caso, tenho cerca de 23 tabelas com média 15 à 30 campos em cada uma delas. Como tenho os meus objetos na minha aplicação e não queria criar uma DAL/DAO para resgatar os dados e assim popular cada um dos meus objetos, e estes por sua vez adicionar à coleção adotamos o NHibernate (ferramenta de OR/M), e com isso, diminuiu à 0 (Zero) a escrita de códigos T-SQL e Stored Procedures.

Por fim, é criado as Threads que são necessários para que essas “rotinas” corram “side-by-side”, e assim aumentando a facilidade e produtividade interna da empresa que está a utilizar este WinService.