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.

DTC e Virtualização

Recentemente tive um problema curioso devido a virtualização de servidores. O pessoal responsável pela infraestrutura utiliza a mesma base/imagem do sistema operacional quando um novo servidor é criado. Até então, sem nenhum problema aparente.

Como a aplicação que estava sendo instalada trata-se de um processo distribuído, com Message Queues, SQL Server e Windows Services, o DTC é necessário para coordenar todos os processos transacionais. Quando a aplicação roda, todas as transações estavam sendo abortadas.

Depois de uma busca aprofundada, encontrei o motivo do problema: há dentro do registro do Windows uma GUID que identifica o DTC. Justamente pelo fato de que todas as máquinas utilizam uma mesma base comum, essa GUID estava igual em todas elas. A solução para o problema, encontrada no final deste artigo, é criar uma nova GUID (via algum utilitário, como aquele do Visual Studio .NET) e renomear a chave que tem como Description MSDTC dentro de HKEY_CLASSES_ROOTCID em cada máquina participante do processo.

Eis aqui maiores informações sobre ghosting, mas não me compete. 😉

WCF – Consumindo serviços no AJAX

Uma das grandes partes do .NET Framework 3.0 foi o WCF – Windows Communication Foundation. Quando ele foi lançado, várias formas de acessar os serviços WCF também foram disponibilizadas. Entre as formas, conhecidas como endpoints, podemos citar algumas, tais como: HTTP, TCP e MSMQ. Com a vinda do ASP.NET AJAX, surgiu a necessidade de consumir serviços WCF diretamente dentro deste tipo de aplicação.

Através do Visual Studio .NET 2008 e o .NET Framework 3.5, a Microsoft se preocupou com a necessidade de consumir serviços WCF no AJAX e aproveitou esta oportunidade para criar um binding. Este binding, chamado de 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.

Para possibilitar a chamada de serviços WCF através do AJAX é necessário, primeiramente, a criação da estrutura do serviço, ou seja, a criação da Interface, decoradas com os atributos ServiceContract e OperationContract; além disso, a criação de um arquivo *.svc para que seja possível acessá-lo. Até este momento, não há nada de diferente em relação a criação de um serviço que será exposta via protocolo HTTP, usando como host o IIS. As mudanças começam efetivamente a partir daqui, ou seja, a configuração do endpoint no arquivo Web.Config e do controle ScriptManager.

Como foi mencionado acima, devemos recorrer a utilização do binding webHttpBinding. O único detalhe que você deve se atentar é na configuração do behavior do endpoint, onde devemos informar o elemento enableWebScript para habilitar o acesso via AJAX. O trecho de código abaixo ilustra a configuração do serviço e do endpoint no arquivo Web.Config:

<system.serviceModel>
  <services>
    <service name="Usuario" behaviorConfiguration="SrvBehavior">
      <endpoint
        address="mex"
        binding="mexHttpBinding"
        contract="IMetadataExchange"/>
      <endpoint
        binding="webHttpBinding"
        behaviorConfiguration="EdpBehavior"
        contract="IUsuario"/>
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="SrvBehavior">
        <serviceMetadata httpGetEnabled="true"/>
        <serviceDebug includeExceptionDetailInFaults="false"/>
      </behavior>
    </serviceBehaviors>
    <endpointBehaviors>
      <behavior name="EdpBehavior">
        <enableWebScript />
      </behavior>
    </endpointBehaviors>
  </behaviors>
</system.serviceModel>

Uma vez configurado o serviço, chega o momento de consumí-lo no cliente, ou melhor, através do código Javascript. O primeiro passo é definir um controle ScriptManager na página onde deseja consumir o serviço; esse controle gerencia as bibliotecas e arquivos de scripts e, entre outras funcionalidades, a geração de proxies para o consumo de serviços via AJAX. Esse controle possui uma coleção de serviços chamada Services, onde cada um dos elementos, do tipo ServiceReference, corresponde a um serviço que será exposto e consumido pelo cliente.

O próximo passo é, através do código Javascript, invocar o código no cliente. Assim como acontecia com o Client-Side Callbacks, você cria uma função que servirá como ponto de entrada e será disparada através de um evento, também client-side; na implementação desta função, você efetivamente invocará o serviço WCF, mas na verdade é método do proxy, que foi criado pelo controle ScriptManager. O proxy cria os métodos que são disponibilizados pelo serviço, mas adiciona mais três parâmetros, que são: o ponteiro para um método de callback que será disparado quando o método retornar; um ponteiro para um método que será disparado caso alguma exceção for disparada quando o método for executado e, finalmente, um objeto que servirá como contexto e será passado para todos os métodos durante a execução. O código abaixo mostra a página ASPX devidamente configurada para invocar o serviço WCF:

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
    
        function ExibirBoasVindas()
        {
            var proxy = new tempuri.org.IUsuario();
            proxy.BoasVindas('Israel', Resultado, null, null);
        }
        
        function Resultado(result)
        {
            alert(result);
        }
    
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Services>
                <asp:ServiceReference Path="~/UserService.svc" />
            </Services>
        </asp:ScriptManager>
        <input
            id="Button1"
            type="button"
            value="button"
            onclick="javascript:ExibirBoasVindas();" />
    </form>
</body>
</html>

Como podemos notar, definimos o serviço WCF (arquivo *.svc) no interior do controle ScriptManager que será responsável por criar, em tempo de execução, o proxy para o consumo via AJAX. Já o tempuri.org trata-se do namespace padrão, qual você pode customizar através do da propriedade Namespace do atributo ServiceContract e ainda, se você desejar visualizar o proxy que é criado, pode acessar o serviço WCF da seguinte forma: http://localhost:1029/Projeto/UserService.svc/js, ou seja, adicionar o /js no final do endereço do serviço. Isso permitirá que você efetue o download do proxy que é gerado em tempo de execução.

Importante: Também temos uma nova factory definida dentro do namespace System.ServiceModel.Activation, chamada WebScriptServiceHostFactory que está presente no .NET Framework 3.5. Definindo essa factory no atributo ServiceHost do arquivo *.svc, elimina qualquer configuração no arquivo Web.Config da aplicação, já que ela configura todos os requisitos necessários (behaviors, binding, etc.) para o consumo do serviço via AJAX.

Chamadas entre domínios

É importante dizer que o objeto XMLHttpRequest não permite efetuar chamadas para serviços que estão fora dos limites do servidor local. Sendo assim, você não poderá referenciar serviços que estão em outros domínios ou sub-domínios e mudança de protocolos ou portas. Podemos, através da imagem abaixo, visualizar essas restrições:

Figura 1 – Invocando serviços além dos limites da aplicação.

Como podemos ver, não podemos invocar qualquer serviço que está além da aplicação local mas, se por algum motivo, desejarmos fazer isso, então devemos proceder da mesma forma que vimos no decorrer deste artigo porém, o serviço WCF local que devemos criar, servirá de proxy para o serviço que está em algum outro local.

Conclusão: Os serviços WCF fornecem uma grande flexibilidade em cenários que precisam, de alguma forma, expor informações ou componentes para que terceiros possam consumí-los. Finalmente, com a vinda do Visual Studio .NET 2008 e o .NET Framework 3.5, podemos agora invocar esses serviços através de aplicações AJAX, garantindo assim uma interatividade muito melhor, sem a necessidade de recorrer a postbacks para a execução de recursos como estes.

Client Application Services

A nova versão do Visual Studio (codename “Orcas”) traz uma série de novas funcionalidades a nível de linguagens e de aplicações. No caso das linguagens, podemos citar algumas técnicas que já foram abordadas neste outro artigo quando falamos sobre o LINQ. Quando falamos a nível de aplicações, muitas funcionalidades foram criadas e, uma delas, que é chamada de Client Application Services, é tema deste artigo.

Client Application Services trata-se de um novo conjunto de funcionalidades que habilita os mais diversos tipos de aplicações a autenticar usuários, recuperar suas respectivas roles e ainda persistir informações de cada um desses usuários em um servidor qualquer. A principal vantagem do Client Application Services é que ele trabalha em conjunto com uma aplicação Web, ou seja, você pode ter uma aplicação Web que disponibiliza todos esses serviços para os usuários que a acessam e também disponibilizar essa mesma infra-estrutura para que, por exemplo, uma aplicação Windows Forms possa fazer o uso disso, centralizando todo o acesso e gerenciamento de usuários em um único local.

É importante dizer que este artigo está baseado na versão Beta 1 do “Orcas” (versão 3.5 do .NET Framework) e está sujeito a mudanças até que a versão final seja lançada. A maior parte de todas as classes que precisamos manipular para habilitar esse recurso estão contidas dentro do namespace System.Web.Extensions (System.Web.Extensions.dll) que também possui toda a funcionalidade server-side do ASP.NET AJAX e que, por padrão, é automaticamente adicionada ao GAC (Global Assembly Cache) durante a instalação do .NET Framework.

As classes que iremos utilizar no decorrer do artigo estão (grande parte delas) divididas entre três principais namespaces, a saber: System.Web.ApplicationServices, System.Web.ClientServices e System.Web.ClientServices.Providers. O primeiro deles, System.Web.ApplicationServices, contém classes que são utilizadas do lado do servidor e que são a “ponte” entre a requisição e o recurso solicitado; já o namespace System.Web.ClientServices possui duas classes que representam uma principal e uma identity para as aplicações que fazem o uso do Client Application Services e, além delas, há uma classes para trabalho offline que veremos mais tarde, ainda neste artigo. Finalmente, temos o namespaceSystem.Web.ClientServices.Providers que, por sua vez, além de possuir várias classes importantes para o runtime fornece os providers para que o cliente possa especificar na aplicação. Isso é algo bem semelhante ao que já acontece com a configuração do membership em uma aplicação Web, com exceção de que a requisição é encaminhada para o servidor que centraliza esses dados. A imagem abaixo ilustra todas as classes que compõem cada um desses namespaces:

Figura 1 – Classes que compõem os namespaces que citamos acima.

Para que possamos disponibilizar essa funcionalidade, devemos primeiramente configurar a aplicação Web, ou seja, definindo os providers de membership, roles e profile. Neste passo não há nenhum segredo em relação ao que já fazíamos na versão 2.0 do ASP.NET. Sendo assim, eu irei assumir que você já tenha os devidos conhecimentos com relação a isso e, caso não possua, aconselho ler este artigo antes de prosseguir com esta leitura.

Já para o nosso exemplo eu irei utilizar os providers que se baseiam em um banco de dados SQL Server, a saber: SqlMembershipProvider, SqlRoleProvider e SqlProfileProvider. O artigo faz uso de uma base de dados chamada aspnetdb.mdf que faz o uso do SQL Server 2005 Express e foi executado o utilitário aspnet_regsql.exe para criar toda a infra-estrutura necessária dentro da base de dados para suportar todas essas funcionalidades. É importante dizer também que você não verá explicitamente definido cada um dos providers no arquivo Web.Config pois, como estou utilizando as configurações padrões, todos eles já estão definidos “em nível de máquina”, ou melhor, no arquivo machine.config.

Quando criamos uma aplicação Web dentro do “Orcas”, uma nova seção é embutida por padrão dentro do arquivo Web.Config da aplicação. Esta seção é chamada de system.web.extensions e possui alguns elementos que iremos utilizar neste artigo para que seja possível habilitar as funcionalidades (profile, membership e role) para o Client Application Services. Entre esses atributos, temos:

Elemento Descrição
profileService Permite habilitar o profile para que as aplicações o utilizem. Esse elemento possui um atributo chamado enabled que recebe um valor booleano indicando se o mesmo está ou não ativo. Além deste atributo, ele ainda possui dois outros, não menos importantes, que são listados abaixo:

  • readAccessProperties: Recebe todas as propriedades, separadas por vírgula, que somente terão permissão para leitura. Essas propriedades devem estar especificadas no profile da aplicação Web.
  • writeAccessProperties: Recebe todas as propriedades, separadas por vírgula, que somente terão permissão para escrita. Essas propriedades devem estar especificadas no profile da aplicação Web.
authenticationService Esse elemento também contém um atributo chamado enabled que indica se o serviço de autenticação está ou não ativo.
roleService Assim como os elementos anteriores, o roleService também conta com um atributo chamado enabled que indica se o serviço de roles está ou não ativo.

Formas de Acesso

Esses serviços já estão disponíveis desde as primeiras versões do Atlas que, por sua vez, fornece um proxy que habilita as aplicações baseadas em browser (que também é uma aplicação cliente) acessar esses recursos sem a necessidade de causar postbacks. Se analisarmos os internals, veremos que o trabalho de executar o processamento destes serviços está contido dentro do módulo chamado ScriptModule (namespace System.Web.Handlers). Veremos mais detalhes sobre essa forma de acesso um pouco mais adiante, quando falaremos sobre o consumo dessas funcionalidades em aplicações Windows. Além da forma de acesso anterior, também é possível acessá-los através do WCF (Windows Communication Foundation) onde você pode, via endpoints, expor essas funcionalidades.

Independente de qual seja a forma de acesso que você estiver utilizando, há classes dentro do namespace System.Web.ApplicationServices que contém toda a infra-estrutura para tratar tais requisições. Essas classes são AuthenticationService, RoleService e ProfileService, que já estão devidamente decorados com o atributo ServiceContract para suportar a exposição via WCF. Essas classes delegam a cada um dos providers concretos o trabalho de autenticação, autorização e gerenciamento dos profiles, verificando antes se o recurso está ou não habilitado, verificação que é feita baseando-se nos atributos enabled de cada um dos elementos da tabela acima.

O código abaixo ilustra o arquivo Web.Config da aplicação Web que irá disponibilizar os serviços para que os clientes possam consumí-los; mas vale lembrar que alguns trecho do arquivo foram suprimidos para poupar espaço. Se repararmos, há uma seção chamada system.web.extensions que contém todas as configurações que habilitam cada um dos serviços que desejamos disponibilizar.

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="false">
      <assemblies>
        <add assembly="System.Core"/>
        <add assembly="System.Web.Extensions" />
      </assemblies>
    </compilation>
    <authentication mode="Forms" />
    <httpHandlers>
      <remove verb="*" path="*.asmx"/>
      <add
          verb="*"
          path="*.asmx"
          validate="false"
          type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions" />
      <add
          verb="*"
          path="*_AppService.axd"
          validate="false"
          type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions" />
      <add
          verb="GET,HEAD"
          path="ScriptResource.axd"
          type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions" />
    </httpHandlers>
    <httpModules>
      <add
          name="ScriptModule"
          type="System.Web.Handlers.ScriptModule, System.Web.Extensions" />
    </httpModules>
    <anonymousIdentification enabled="true"/>
    <profile enabled="true">
      <properties>
        <add
            name="Parametro"
            type="System.String"
            allowAnonymous="true"/>
      </properties>
    </profile>
  </system.web>
  <system.web.extensions>
    <scripting>
      <webServices>
        <profileService
            enabled="true"
            readAccessProperties="Parametro"
            writeAccessProperties="Parametro" />
        <authenticationService
            enabled="true"
            requireSSL="false" />
        <roleService
            enabled="true"/>        
      </webServices>
    </scripting>
  </system.web.extensions>
</configuration>

Depois dessa configuração, agora já podemos utilizar os recursos habilitados em nossas aplicações clientes. O “Orcas” acrescentou nas propriedades de vários projetos, entre eles Windows Forms e WPF, uma aba chamada Services. É dentro desta aba que vamos habilitar a utilização dos recursos que queremos acessar em nossa aplicação Windows. A imagem abaixo ilustra essa nova tela:

Figura 2 – Aba Services em projetos cliente.

Como podemos reparar, há um CheckBox que habilita a utilização do Client Application Services. Quando habilitado, todos os campos que estão abaixo ficam disponíveis para que você possa especificar a URL de cada Web site onde está cada uma das respectivas funcionalidades (authentication, authorization e profile). Como no caso do exemplo todas as funcionalidades estão contidas dentro de um único site, a mesma URL é especificada em todos os campos. Uma vez que você salvar as informações, um arquivo App.Config é criado com a especificação dos providers, assim como uma aplicação Web. O código abaixo mostra o arquivo App.Config criado depois de definido os application services:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add
      key="ClientSettingsProvider.ServiceUri"
      value="http://localhost/Beta.WebUI/Profile_JSON_AppService.axd" />
  </appSettings>
  <system.web>
    <membership defaultProvider="ClientAuthenticationMembershipProvider">
      <providers>
        <add
          name="ClientAuthenticationMembershipProvider"
          type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, 
             System.Web.Extensions"
          serviceUri="http://localhost/Beta.WebUI/Authentication_JSON_AppService.axd"
          credentialsProvider="Beta.WinUI.Form1, Beta.WinUI" />
      </providers>
    </membership>
    <roleManager defaultProvider="ClientRoleProvider" enabled="true">
      <providers>
        <add
          name="ClientRoleProvider"
          type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions"
          serviceUri="http://localhost/Beta.WebUI/Role_JSON_AppService.axd"
          cacheTimeout="86400" />
      </providers>
    </roleManager>
  </system.web>
</configuration>

Analisando o código acima, temos uma key chamada ClientSettingsProvider.ServiceUri que armazena o endereço até o local onde o profile está sendo disponibilizado. Em seguida, temos uma infra-estrutura de elementos muito semelhante ao que já existe atualmente em uma aplicação Web, que são os elementos membership e roleMananger. A diferença aqui é que eles não utilizam um provider como SqlMembershipProvider ou o SqlRoleProvider e sim os providers que estão definidos dentro do namespace System.Web.ClientServices.Providers. Como optamos pela autenticação via FormsAuthentication, estaremos utilizando no exemplo o provider ClientFormsAuthenticationMembershipProvider e também o ClientRoleProvider para o gerenciamento das roles. A principal diferença nestes providers é que eles invocam o método remotamente, e a forma de acesso dependerá de como você disponibilizou essas funcionalidades na aplicação Web, ou seja, da forma “tradicional”, que é através de uma requisição HTTP (via classes HttpWebRequest e HttpWebResponse), serializando os dados utilizando JSON ou, se desejar, via WCF. É importante mencionar que é necessário que você faça referência aos Assemblies System.Web.dll e System.Web.Extensions.dll na aplicação cliente.

Em ambos os casos, membership e roleManager, há um atributo chamado serviceUri que especifica o URL até a aplicação que disponibiliza o serviço. São os valores definidos para este atributo que o runtime determina para onde e como fazer a requisição. Se o endereço finalizar com *.svc (que identifica um endpoint WCF) a requisição é realizada através do WCF; caso contrário, a requisição é feita via requisição HTTP/JSON, e o que determinará qual será o serviço a ser executado (membership, roles ou provider) é o “nome do recurso” solicitado: Authentication_JSON_AppService.axd, Role_JSON_AppService.axd ou Profile_JSON_AppService.axd.

No caso do elemento membership há um atributo que merece ser comentado. Trata-se do atributo credentialsProvider, que também pode ser definido a partir da aba Service que vimos um pouco acima. Esse atributo permite-nos definir o Type de algum objeto da aplicação que fornecerá uma instância do tipo ClientFormsAuthenticationCredentials representando as credenciais do usuário, que será enviada para a aplicação Web quando o método de Login for invocado. O tipo que for definido obrigatoriamente deve implementar a Interface IClientFormsAuthenticationCredentialsProvider que possui apenas um método chamado GetCredentials que, muitas vezes, é utilizado no formulário de login da aplicação. O trecho de código abaixo ilustra a utilização dessa técnica em um formulário Windows Forms que implementa a Interface IClientFormsAuthenticationCredentialsProvider:

using System.Windows.Forms;
using System.Web.Security;
using System.Web.ClientServices.Providers;

public partial class Login : Form, IClientFormsAuthenticationCredentialsProvider
{
    private void btnLogin_Click(object sender, EventArgs e)
    {
        if(Membership.ValidateUser(string.Empty, string.Empty))
        {
            string[] roles = Roles.GetRolesForUser("IA");
            this.lblMensagem.Text = "Usuário válido";
        }
        else
        {
            this.lblMensagem.Text = "Usuário inválido";
        }
    }

    public ClientFormsAuthenticationCredentials GetCredentials()
    {
        return new ClientFormsAuthenticationCredentials(
            this.txtLogin.Text.Trim(), 
            this.txtPassword.Text.Trim(),
            false);
    }
}

Dentro do evento Click do botão btnLogin invocamos o método estático ValidateUser da classe Membership (System.Web.Security) que retorna um valor booleano indicando se as credenciais informadas são ou não válidas. O ponto curioso é que ao invés de passarmos o login e senha para este método, passamos os valores em branco. Isso acontece porque as credenciais serão extraídas automaticamente a partir do tipo especificado no atributo credentialsProvider do elemento membership.

Além disso, podemos reparar que fazemos a utilização das classes Membership e Roles, assim como já acontece em aplicações Web. A classe Membership trata-se de estática que possui um membro interno chamado s_Provider do tipo MembershipProvider (a classe base para as classes concretas de Membership, como por exemplo o SqlMembershipProvider), o qual receberá a instância da classe concreta. Essa inicialização acontece quando um método interno chamado Initialize desta mesma classe é executado. Ele se encarrega de extrair o provider especificado no arquivo Web.Config (que no nosso caso é o ClientFormsAuthenticationMembershipProvider) e instanciá-los para que, quando chamarmos os métodos e propriedades, já sejam efetivamente os métodos e propriedades da classe concreta que queremos utilizar. O funcionamento é também semelhante para a classe Roles.

Todos os providers específicos e que são utilizados pelo Client Application Services adotam a mesma hierarquia em relação aos outros providers que já conhecemos, ou seja, no caso do membership, as classes ClientFormsAuthenticationMembershipProvider e ClientWindowsAuthenticationMembershipProvider herdam de uma classe abstrata chamada MembershipProvider; já a classe ClientRoleProvider herda da classe abstrata RoleProvider e, finalmente, a classe ClientSettingsRoleProvider herda diretamente de SettingsProvider. Como podemos perceber, os providers que são prefixados com o valor “Client” são os providers utilizados pelas Client Applications e estão disponíveis dentro do namespace System.Web.ClientServices.Providers. A imagem abaixo ilustra a hierarquia destas classes:

Figura 3 – Hierarquia dos providers cliente.

A diferença entre os providers de autenticação (ClientFormsAuthenticationMembershipProvider e o ClientWindowsAuthenticationMembershipProvider) é que o primeiro deles, deve ser utilizado em aplicações cliente quando a forma de autenticação da aplicação Web é via FormsAuthentication; já a segunda é quando a forma de autenticação da aplicação Web se baseia na autenticação do Windows.

Importante: É importante dizer que apesar de vários métodos da classe Membership e Roles estarem disponíveis, muitos deles não são suportados “by design”. Sendo assim, atente-se em chamar os métodos destas classes e certifique-se de que eles realmente possuem a implementação da funcionalidade a qual cada um deles se destina a realizar.

Para finalizar a parte de autenticação e autorização, não podemos deixar de comentar sobre o ClientFormsIdentity e o ClientRolePrincipal. Essas duas classes implementam as Interfaces IIdentity e IPrincipal, respectivamente. O primeiro deles, ClientFormsIdentity, representa a identidade do usuário que efetuou o login na aplicação; já o ClientRolePrincipal refere aos direitos que o usuário possui na aplicação. A instância da classe ClientFormsIdentity é definida automaticamente para a propriedade CurrentPrincipal da classe Thread quando o login é efetuado com sucesso. Essas duas classes estão contidas dentro do namespace System.Web.ClientServices.

Através da Figura 2 podemos notar que o último campo que consta na aba Services é onde devemos informar a URL para o site que disponibiliza as informações de profile. O funcionamento interno para requisitar as informações remotas é bem parecido ao que já acontece com o membership e as roles. A novidade aqui é que, na aba Settings das propriedades do projeto, existe um novo botão chamado Load Web Settings que, através da URL informada anteriormente, irá requisitar as informações (inclusive metadados) e para cada propriedade que está sendo exposta pelo profile da aplicação Web requisitada, ele os criará na seção de Settings da aplicação.

Para todo item criado na seção Settings, independente se ele é ou não oriundo do profile de uma aplicação Web, é sempre criado uma propriedade com o mesmo nome e tipo em um arquivo (Visual C# ou Visual Basic .NET) de “code-behind”. O que difere entre as propriedades “locais” e as propriedades que foram importadas do projeto Web é que, neste segundo caso, ela é decorada com um atributo chamado SettingsProviderAttribute, passando para o seu construtor o provider onde essa informação será efetivamente armazenada que, no nosso caso, é o ClientSettingsProvider. A imagem exibe a aba Settings em que podemos importar as propriedades do profile da aplicação Web previamente especificada na aba Services.

Figura 4 – Importando as propriedades da aplicação Web para a aplicação cliente.

Com isso realizado, já posso acessar ou definir as informações nessas propriedades (dependendo do que foi definido na aplicação Web). Para isso, basta simplesmente acessar como já estávamos fazendo com uma propriedade simples. O exemplo abaixo está também baseado no arquivo Web.Config da aplicação Web que foi criada mais acima. Lá você poderá comprovar a existência da propriedade chamada Parametro que foi definida no profile.

Properties.Settings.Default.Parametro = "123";
MessageBox.Show(Properties.Settings.Default.Parametro);

Acessando via WCF

Como foi mencionado acima, é possível também expor as funcionalidades de membership, roles e profiles a partir de serviços WCF (*.svc). Isso exigirá uma ligeira mudança na aplicação Web que expõe essas funcionalidades, ou seja, será necessário adicionar a este mesmo projeto um arquivo com extensão *.svc para cada uma das funcionalidades que deseja disponibilizar.

Para fazer o mesmo exemplo que vimos acima, mas utilizando WCF, devemos adicionar três arquivos ao projeto Web. Chamaremos cada um desses arquivos de: AuthenticationService.svc, RoleService.svc e ProfileService.svc. Para cada um desses arquivos é necessário alterar o valor do atributo Service da diretiva do mesmo, informando o tipo que será exposto. Como cada um dos arquivos tem uma finalidade diferente, então a configuração para cada um deles ficará:

//Arquivo AuthenticationService.svc
<% @ServiceHost Language="C#" Debug="true"
        Service="System.Web.ApplicationServices.AuthenticationService" %>

//Arquivo RoleService.svc
<% @ServiceHost Language="C#" Debug="true"
        Service="System.Web.ApplicationServices.RoleService" %>

//Arquivo ProfileService.svc
<% @ServiceHost Language="C#" Debug="true"
        Service="System.Web.ApplicationServices.ProfileService" %>

A única diferença que muda no código acima para uma aplicação que está sendo desenvolvida em Visual Basic .NET é que o atributo Language deve ser definido com o valor “VB”. Neste cenário não precisamos nos preocupar com a infra-estrutura necessária (atributos ServiceContract e OperationContract) para expor uma classe via WCF, pois tudo isso já está definido nas classes AuthenticationService, RoleService e ProfileService.

Uma vez definidos os arquivos físicos, é necessário configurar os endpoints no arquivo Web.Config da mesma aplicação. Neste momento vamos definir para cada tipo de serviço o seu respectivo nome, binding e o contrato. Como o foco não é explicar cada um dos detalhes do WCF, vou assumir que você já conheça essas técnicas. Caso você ainda não esteja familiarizado com o WCF, então é necessário que você leia algum material a respeito para que possa entender o seu funcionamento e ajustar as configurações do mesmo de acordo com a sua necessidade.

O trecho de código abaixo ilustra todos os endpoints necessários para expor cada uma das funcionalidades que vimos até o momento. Reparem que o contrato que está definido para cada um desses endpoints reflete exatamente no nome da classe concreta que, como já sabemos, já expõe o contrato porque esta decorada com os atributos necessários.

<?xml version="1.0"?>
<configuration>
  <system.web>
    <authentication mode="Forms" />
    <anonymousIdentification enabled="true"/>
    <profile enabled="true">
      <properties>
        <add name="Parametro" type="System.String" allowAnonymous="true"/>
      </properties>
    </profile>
  </system.web>
  <system.web.extensions>
    <scripting>
      <webServices>
        <profileService
            enabled="true"
            readAccessProperties="Parametro"
            writeAccessProperties="Parametro" />
        <authenticationService
            enabled="true"
            requireSSL="false" />
        <roleService
            enabled="true"/>        
      </webServices>
    </scripting>
  </system.web.extensions>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <services>
      <service
        behaviorConfiguration="AuthenticationBehavior"
        name="System.Web.ApplicationServices.AuthenticationService">
        <endpoint
          binding="basicHttpBinding"
          name="AuthenticationService"
          contract="System.Web.ApplicationServices.AuthenticationService" />
      </service>
      <service
        behaviorConfiguration="RoleBehavior"
        name="System.Web.ApplicationServices.RoleService">
        <endpoint
          binding="basicHttpBinding"
          name="RoleService"
          contract="System.Web.ApplicationServices.RoleService" />
      </service>
      <service
        behaviorConfiguration="ProfileBehavior"
        name="System.Web.ApplicationServices.ProfileService">
        <endpoint
          binding="basicHttpBinding"
          name="ProfileService"
          contract="System.Web.ApplicationServices.ProfileService" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="AuthenticationBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
        <behavior name="RoleBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
        <behavior name="ProfileBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Importante: Mesmo fazendo o acesso via WCF, é necessário que as funcionalidades que vão ser disponibilizadas estejam devidamente habilitadas. Caso contrário, uma exceção do tipo InvalidOperationException é atirada para o cliente, informando que o recurso está desabilitado.

A configuração do Client Application Services quando estamos expondo as funcionalidades através de WCF é exatamente a mesma quando estamos falando do lado do cliente (quem consome essas funcionalidades) em relação ao exemplo anterior. Basicamente, a única diferença está na configuração do arquivo App.Config do lado do cliente onde, no atributo serviceUri, você deverá especificar a URL do serviço WCF (*.svc) ao invés do *.axd, que é colocado por padrão quando não é informado. Internamente, o runtime determina a forma de requisição analisando a extensão do recurso (arquivo) informado neste atributo.

Versão Beta 1 – Orcas

A configuração do Client Application Services utilizando WCF como forma de acesso infelizmente não está funcionando na versão Beta 1 do Visual Studio Orcas. Apesar da configuração externa ser o que vimos acima, internamente há alguns problemas com relação ao contrato dos serviços que impedem que a requisição seja efetuada com sucesso. Inclusive essa falha já foi reportada para a Microsoft e, provavelmente, será consertada nas próximas versões.

Se desejarmos, há uma alternativa onde podemos acessar os serviços WCF diretamente, assim como já fazemos. Essa técnica permitirá acessarmos as funcionalidades que estão sendo disponibilizadas pela aplicação Web. O ponto negativo que vejo aqui é que, desta forma, nós ficamos responsáveis por criar e definir a identity e a principal da thread que é encarregada pela execução da aplicação, o que acontece automaticamente quando utilizamos o ClientFormsAuthenticationMembershipProvider. Uma vez que referenciamos todos os serviços em um único cliente, temos:

Figura 5 – Serviços WCF instalados no cliente.

Com os serviços WCF devidamente referenciados na aplicação cliente, antes de consumí-los dentro da mesma é necessário abrir o arquivo App.Config desta mesma aplicação e definir o atributo allowCookies do binding especificado para true para que seja possível o WCF armazenar cookies no cliente e utilizá-los em futuras requisições. Depois disso, basta instanciarmos as classes que, na verdade, são os proxies de cada um dos serviços e chamar seus respectivos métodos. O trecho de código abaixo ilustra como devemos proceder para validar e autenticar um determinado usuário:

using (AuthenticationService.AuthenticationServiceClient auth =
    new AuthenticationService.AuthenticationServiceClient())
{
    if (auth.ValidateUser("Israel", "P@$$w0rd", string.Empty) &&
        auth.Login("Israel", "P@$$w0rd", string.Empty, true))
    {
        try
        {
            //Se o atributo allowCookies for definido como
            //False, o valor retornado pelo método IsLoggedIn
            //também será False.

            Console.WriteLine(auth.IsLoggedIn().ToString());
        }
        finally
        {
            auth.Logout();
        }
    }
}

Trabalhando em modo Offline

Um recurso que o Client Application Services também já possui é a possibilidade de trabalhar em modo offline. Esse recurso possibilita efetuar uma requisição para um serviço (seja ele membership, roles ou profile). Enquanto estiver no modo online, os dados relevantes são armazenados localmente para quando estiver offline. Quando você está em modo offline não haverá possibilidade de acessar um determinado recurso e, sendo assim, a sua aplicação poderia falhar. Felizmente o armazenamento local facilita isso, sem a necessidade de termos que escrever código para serializar/deserializar os objetos.

Há dois tipos de repositórios que são disponibilizados por esse recurso: sistema de arquivos ou um banco de dados local. Por padrão, será utilizado o sistema de arquivos, mas você pode determinar uma base de dados local (SQL Server Express) para armazenar essas informações. Para isso, você deve ir novamente até a aba Services do projeto cliente e, ao habilitar o Client Application Services, um botão chamado Advanced será habilitado e, ao clicar, você terá acesso a tela mostrada na figura abaixo, onde você pode marcar a opção User a database instead of the file system for local data storage. Ao marcá-la, o campo abaixo será disponibilizado para que você informe a connection string até o mesmo.

Figura 6 – Habilitando o armazenamento em um banco de dados.

Finalmente, o que vai determinar se a aplicação está ou não online é através da propriedade IsOffline da classe estática ConnectivityStatus que, por sua vez, está contida dentro do namespace System.Web.ClientServices. Quando desejar, basta você atribuir o valor True a esta propriedade, e assim os dados passarão a ser resgatados/atualizados localmente, dentro do repositório selecionado.

Conclusão: Uma das principais features que foram pedidas em fóruns e talvez à própria Microsoft era uma forma simples de conseguir integrar aplicações clientes, como Windows Forms, com toda a extensível infra-estrutura fornecida, inicialmente, para uma aplicação Web, que são os Provider Models. Felizmente isso foi acatado como uma necessidade pela Microsoft e nesta nova versão do .NET Framework/Visual Studio .NET já está disponível para a utilização e, no decorrer deste artigo, pudemos entender um pouco mais sobre suas configurações e também o seu comportamento interno.

WCF – Known Types

É muito comum em qualquer linguagem orientada a objetos, criarmos uma classe base e que, a partir dela, criar classes derivadas. Além disso, um dos grandes benefícios que temos com a orientação a objetos é a possibilidade de declararmos uma variável do tipo da classe base e atribuirmos a ela uma instância de uma classe concreta e, da mesma forma, podemos ter uma função em que em seus parâmetros os seus tipos são especificados com o tipo da classe base e, conseqüentemente, podemos também passar instâncias das classes derivadas.

Infelizmente não funciona da mesma forma quando falamos de serviços que são expostos a partir do WCF. Neste cenário, por padrão, você não pode usar uma classe derivada ao invés de uma classe base. Sendo assim, se quisermos utilizar esta classe base publicamente (parâmetros e retorno de métodos), precisamos nos atentar em algumas técnicas para permitir isso. Para exemplificar o problema, vamos analisar o código contrato abaixo:

using System;
using System.ServiceModel;

namespace DevMinds.Library
{
    [ServiceContract]
    public interface IGerenciadorDeContatos
    {
        [OperationContract]
        void AdicionarContato(Pessoa p);

        [OperationContract]
        Pessoa[] RecuperaContatos();
    }
}

E, além do contrato, temos a classe Pessoa que possui apenas uma propriedade do tipo string:

using System;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace DevMinds.Library
{
    [DataContract]
    public class Pessoa
    {
        private string _nome;
        
        [DataMember]
        public string Nome
        {
            get
            {
                return this._nome;
            }
            set
            {
                this._nome = value;
            }
        }
    }
}

Supondo-se que o cliente defina uma classe chamada Fisica (de pessoa física) que herde diretamente da classe Pessoa e tente enviar a instância desta classe para o método AdicionarContato. Apesar de compilar, você terá uma exceção quando o código for executado, pelo fato de que quando você passar a classe Fisica ao invés de Pessoa o serviço não saberá como deserializar a “Pessoa” que é recebida. O mesmo vale para quando você tem uma coleção em seu serviço de uma classe derivada e tenta retorná-la para o cliente, expondo através do retorno do método uma coleção de tipos base.

Para suavizar este problema, o WCF introduziu um atributo chamado KnownType. Esse atributo recebe em seu construtor um Type, indicando à infra-estrutura do WCF que existe uma classe derivada do tipo onde o atributo é aplicado e que ela também pode ser aceita. Quando você define este atributo do lado do servidor, você permitirá que todos os contratos e operações que utilizem este tipo base possam aceitar o tipo especificado pelo atributo. O exemplo abaixo ilustra como devemos proceder para utilizar o atributo KnownType:

[DataContract]
[KnownType(typeof(Fisica))]
public class Pessoa
{
    //Implementação
}

[DataContract]
public class Fisica : Pessoa
{
    //Implementação
}

Uma vez aplicado este atributo, ele fará com que a classe derivada seja adicionada nos metadados do serviço e, conseqüentemente, o cliente terá a definição da mesma, podendo passá-la para o serviço ao invés da classe base. Desta forma, podemos tranquilamente executar o código que antes do atributo era impossível:

using (GerenciadorDeContatosClient proxy = new GerenciadorDeContatosClient())
{
    Fisica f = new Fisica();
    f.Nome = "Israel";
    f.Cpf = "00000000000";
    proxy.AdicionarContato(f);
}

Como disse anteriormente, o ponto negativo deste atributo é com relação ao escopo, pois ele permite que todos os locais onde aceite uma classe base, aceitar um tipo derivado especificado no atributo. Se não quisermos isso, ou seja, se desejarmos habilitar este recurso somente para uma determinada operação, um contrato ou um serviço, podemos utilizar o atributo ServiceKnowType, que pode ser aplicado a um método, Interface ou classe. Com isso, o nosso código mudará ligeiramente, permitindo que somente um determinado método aceite instâncias da classe Fisica:

using System;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace DevMinds.Library
{
    [ServiceContract]
    public interface IGerenciadorDeContatos
    {
        [OperationContract]
        [ServiceKnowType(typeof(Fisica))]
        void AdicionarContato(Pessoa p);

        //outros métodos
    }
}

E ainda, se quiser permitir mais de uma classe derivada de um tipo base, então poderá adicionar múltiplos atributos (KnowType ou ServiceKnowType) para satisfazer a todos os tipos que a operação poderá aceitar/retornar. O trecho de código abaixo ilustra múltiplos atributos para mais de um tipo derivado:

using System;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace DevMinds.Library
{
    [ServiceContract]
    public interface IGerenciadorDeContatos
    {
        [OperationContract]
        [ServiceKnowType(typeof(Fisica))]
        [ServiceKnowType(typeof(Juridica))]
        void AdicionarContato(Pessoa p);

        //outros métodos
    }
}

Para finalizar, ainda temos um detalhe importante quando falamos de know types que é em relação à recompilação do código cliente ou do serviço quando alguma nova classe derivada deve ser informada para que ela possa ser utilizada. Uma vez que um novo tipo precisa ser utilizado, implica em mudar o código, recompilá-lo e redistribuí-lo. Para amenizar isso, o WCF permite-nos fazer essa configuração, ou melhor, adição de novos tipos, a partir do arquivo de configuração, como é mostrado através do trecho de código abaixo:

<system.runtime.serialization>
  <dataContractSerializer>
    <declaredTypes>
      <add type="DevMinds.Library.Pessoa, DevMinds.Library, Version=1.0.0.0, PublicKeyToken=null">
        <knownType
          type="DevMinds.Client.Juridica, DevMinds.Client, Version=1.0.0.0, PublicKeyToken=null"/>
      </add>
    </declaredTypes>
  </dataContractSerializer>
</system.runtime.serialization>

Conclusão: Mais uma vez vimos através de uma pequena funcionalidade o quanto o WCF é flexível e permite de uma forma bem fácil e simples integrar o código e seus tipos que são desenhados e executados no cliente com o código que temos no servidor.

DTC e Windows XP SP2

Hoje pela manhã estava fazendo um teste em um processo transacionado que estamos criando na empresa onde trabalho. Entre algumas tarefas, temos a leitura de informações (mensagens) que estão contidas dentro do Message Queue e, depois de analisado o conteúdo da mesma, o seu conteúdo é salvo em uma base de dados SQL Server 2005 (fisicamente separado), qual encontra-se hospedado em um Windows Server 2003 R2.

Ao efetuar o primeiro teste, pude notar que uma exceção aconteceu informando que o acesso remoto ao DTC estava desabilitado. Para resolver isso, não tem muito segredo. Basta ir até o Component Services, clicar com o botão direito do mouse em cima do ícone Computer e ir até as propriedades do mesmo. Depois disso, há uma aba chamada MS DTC que, no fim dela, há um botão denominado Security Configuration. Ao clicar, uma nova tela é aberta e voce precisa habilitar a opção Network DTC Access. O mesmo deve ser feito do lado de servidor se não estiver habilitado. Somente atentem-se que ao fazer qualquer alteração, o serviço do DTC sofre um stop-and-go, parando também o serviço do Message Queue que, obrigatoriamente, voce deverá inicializar.

Depois disso resolvido, ao executar novamente o processo, recebo a seguinte exceção: Unhandled Exception: System.Transactions.TransactionException: The transaction has already been implicitly or explicitly committed or aborted. —> System.Runtime.InteropServices.COMException (0x8004D00E): The transaction has already been implicitly or explicitly committed or aborted (Exception from HRESULT: 0x8004D00E).

O servidor não continha nenhum problema. O DTC do mesmo estava iniciado, não tinha problemas para acessá-lo via IP ou via nome; em resumo, decidi que o problema não estava no servidor. Depois de alguma pesquisa pela internet, encontrei alguns casos onde o pessoal estava dizendo que os envolvidos deveriam estar dentro do mesmo domínio mas, no meu caso, ambos (cliente e servidor) não tinham nenhum domínio definido.

Como o sistema operacional do meu notebook é WinXP Pro com SP2, passei a duvidar que havia alguma restrição de segurança no meu notebook. E não deu outra, foi adicionar o executável %windir%system32msdtc.exe na lista de exceção do Firewall do Windows XP que resultou com sucesso.

WCF – Tracing

Toda e qualquer tipo de aplicação sempre exige uma forma de armazenar possíveis erros que possam acontecer durante a sua execução. Isso irá ajudar imensamente para diagnosticarmos problemas que ocorrem e, conseqüentemente, facilitar na sua solução. Isso não é diferente em serviços WCF. Esse artigo tem a finalidade de demonstrar a integração que os serviços WCF possibilitam para a captura e persistência dos erros para uma posterior análise.

Felizmente, o WCF já têm embutido em sua infraestrutura uma integração muito forte com o namespace System.Diagnostics que, por sua vez, fornece toda a parte de monitoramento da “saúde” das aplicações construídas sob a plataforma .NET. O monitoramento não está habilitado por padrão e, para isso, basta configurarmos algumas informações necessárias para o funcionamento do mesmo dentro do arquivo *.config.

É bom informar que, como pré-requisito para este artigo, você precisa conhecer as principais funcionalidades que são disponibilizadas pelo System.Diagnostics. O WCF já define alguns trace sources que você pode estar utilizando para efetuar os logs. Entre os trace sources disponíveis temos um chamado System.ServiceModel. Este trace source loga todos os estágios de um processamento de um serviço WCF, que vai desde a criação do mesmo, passando pela autenticação e transporte da mensagem, até o retorno do método. O código abaixo trata-se de um trecho do arquivo App.Config, que foi extraído de uma aplicação que consome o serviço WCF:

<system.diagnostics>
  <sources>
    <source
      name="System.ServiceModel"
      switchValue="All"
      propagateActivity="true">
      <listeners>
        <add
            name="TraceInXml"
            type="System.Diagnostics.XmlWriterTraceListener"
            initializeData="C:AppLog.svclog" />
      </listeners>
    </source>
  </sources>
  <trace autoflush="true" />
</system.diagnostics>

Analisando o trecho de código acima, especificamos o source como sendo System.ServiceModel para ser capaz de capturar todos os estágios do processamento do WCF. O elemento source possui dois outros atributos, chamados: switchValue e propagateActivity. O primeiro deles, switchValue, recebe uma combinação de valores (separados por vírgula) que especifica quais serão os tipos de informações que serão capturadas, ou seja, uma espécie de filtro. Como há várias possibilidades, a tabela abaixo descreve cada uma delas:

Nível Descrição
Off

Nenhum tipo de informação é capturada.

Critical

Somente eventos considerados “negativos” são capturados, ou seja, eventos que indicam um processamento inesperado ou um erro condicional. Neste cenário, algumas exceções não tratadas são capturadas e devidamente logadas. Entre essas exceções temos: OutOfMemoryException, ThreadAbortException, StackOverflowException, entre algumas outras.

Error

Somente eventos considerados “negativos” são capturados, ou seja, eventos que indicam um processamento inesperado ou um erro condicional. Neste cenário, todo e qualquer tipo de exceção não tratada é capturada e devidamente logada.

Warning

Somente eventos considerados “negativos” são capturados, ou seja, eventos que indicam um processamento inesperado ou um erro condicional. Refere-se a problemas que acontecem na sua aplicação, mas ela é capaz de continuar trabalhando.

Information

Somente eventos considerados “positivos” são capturados, ou seja, eventos que indicam o sucesso de uma determinada tarefa.

Verbose

Somente eventos considerados “positivos” são capturados, mas com a diferença em relação ao Information, de que estes eventos são considerados de “baixo nível”, úteis em procedimento de debugging ou otimização.

ActivityTracing

Permite capturar eventos que estão contidos dentro uma determinada atividade. As atividades são abordadas de forma mais detalhada logo abaixo.

All

Todo e qualquer tipo de evento (que vimos acima) são capturados e devidamente logados.

Observação: Atente-se aos tipos de evento (filtro) que se aplica ao tracing. Utilize isso com uma restrição muito grande, definindo somente o que você precisa realmente capturar em sua aplicação/serviço. Dependendo, você pode estar forçando o runtime a salvar informações desnecessárias, comprometendo a performance da aplicação.

O segundo atributo é o propagateActivity. Ele é utilizado quando o atributo switchValue estiver definido como ActivityTracing, e recebe um valor booleano indicando se a atividade deve ou não ser propagada para os endpoints que participam do processo. Veremos mais detalhes sobre atividades e propagações a seguir. E, finalmente, depois do source configurado, devemos definir um listener (ou vários) que será responsável por persistir as informações em formato XML (quando utilizar o XmlWriterTraceListener) mas, como o WCF se utiliza dos listeners contidos dentro do namespace System.Diagnostics, nada impede de utilizar o TextWriterTraceListener e gerar o output em formato texto.

Atividades

As atividades são unidades de processamento que nos ajudam no processo de identificação de uma possível falha. Essas atividades agrupam todos os traces para cada unidade de processamento, facilitando o rastreamento de possíveis falhas que possam vir a ocorrer durante a execução de um determinado serviço WCF. As atividades estão sempre relacionadas e com o auxílio de uma ferramenta chamada Service Trace Viewer (mais abaixo falaremos sobre ela) poderemos visualizar o processo de forma gráfica.

Quando falamos de atividades, é importante saber que elas podem acontecer tanto no cliente quanto no servidor. Sendo assim, é importante que as atividades entre cliente e servidor também estejam relacionadas para conseguirmos mesclá-las e, conseqüentemente, termos uma visualização completa do processo. Para que isso seja possível, há uma técnica chamada propagação de atividade. Quando esse recurso é habilitado, uma espécie de ID é gerado e enviado no header da mensagem, justamente para correlacionar as atividades através das aplicações. Como vimos no exemplo acima, para habilitarmos a propagação, definimos como True o atributo propagateActivity do elemento source.

Service Trace Viewer Tool (SvcTraceViewer.exe)

Este utilitário nos ajuda a analisar e dignosticar os traces que são gerados pelos serviços WCF disponibilizando, de uma forma bastante simples e intuitiva, a visualização de todo o processo da requisição, dividida em várias atividades. Além disso, ele permite selecionarmos vários arquivos, o que permite escolhermos o trace gerado pelo cliente e servidor, e a própria ferramenta mescla as informações dos arquivos, apresentando-os da forma que elas realmente acontecem.

É importante dizer que essa ferramenta não é disponibilizada apenas com a instalação do .NET Framework 3.0. Para poder obtê-la, é necessário que você faça o download e instale o Microsoft SDK for .NET Framework 3.0, que pode ser encontrado neste endereço.

Figura 1 – Utilizando o utilitário SvcTraceViewer.exe para analisar um tracing gerado pelo cliente e servidor.

Se analisarmos a figura acima, podemos reparar que eu optei por mostrar de forma gráfica todo o processo. Logo na parte superior do gráfico é possível visualizar os processos (cliente e servidor) que fizeram parte da requisição para o serviço. Neste mesmo gráfico ainda é possível analisarmos exatamente o momento da criação do proxy, do host e, o principal, do momento onde a exceção ocorre, que é onde o cursor está posicionado. Ainda a respeito do gráfico, cada um dos ícones que ali consta representa uma determinada ação e, para maiores detalhes sobre cada um deles, consulte este documento.

Esse utilitário permite a criação de projetos (*.stvproj) de forma que podemos agrupar todos os traces relacionados a alguma aplicação/serviço. Além das várias funcionalidades fornecidas por este utilitário, há uma outra que merece uma atenção, que é o Step Forward (F10) ou Step Backward (F9). O primeiro permite você avançar para o próximo trace e o segundo permite voltar para o trace anterior. É bem semelhante ao Step Into e Step Over do debugger do Visual Studio .NET.

Conclusão: Que o trace é essencial em qualquer tipo de aplicação, isso já não é novidade. Felizmente, como pudemos notar neste artigo, o WCF sabe dessa importância e já incorporou em sua infraestrutura todo o procedimento necessário para gerar os logs e disponibilizar para uma posterior análise. Além disso, a utilização da ferramenta Service Trace Viewer torna a compreensão do processo como um todo muito mais legível e fácil de encontrar uma possível falha.