A tecnologia WCF (sigla de Windows Communication Foundation) é parte integrante do .NET Framework, representando a solução da Microsoft para a construção de aplicações seguindo uma Arquitetura Orientada a Serviços (paradigma este também conhecido como SOA, sigla do inglês “Service-Oriented Architecture”). Em termos conceituais, um serviço deve ser compreendido como um componente de software que disponibiliza uma ou mais funcionalidades; cada uma destas pode vir a ser consumida posteriormente outras aplicações.

Grande parte das aplicações criadas em WCF nada mais são do que Web Services. Tratam-se de implementações de serviços que fazem uso de um padrão conhecido como SOAP (Simple Object Access Protocol), as quais podem ser acessadas a partir da Internet, ou até mesmo, em uma rede privada corporativa. Já o protocolo SOAP corresponde, por sua vez, a um formato baseado na linguaguem XML. É mais do que notória a ampla aceitação de XML por parte das principais plataformas de desenvolvimento atuais, com este padrão sendo utilizado em larga escala para o intercâmbio de informações entre softwares com as mais variadas finalidades.

Web Services são empregados geralmente na integração entre sistemas de características bastante heterogêneas, chegando inclusive ao ponto de tornar possível a comunicação entre aplicações construídas em .NET, Java e/ou até mainframe. A esta capacidade de interligar soluções tão diversas, usando para isto um padrão comum para transferência de informações, dá-se o nome de interoperabilidade.

A comunicação com qualquer serviço construído em WCF acontece, obrigatoriamente, por meio de estruturas conhecidas como endpoints. Já um endpoint é formado pelos seguintes elementos:

  • Address: endereço a partir do qual um serviço é acessado por outra aplicação que consumirá o mesmo;
  • Binding: representa o protocolo de comunicação utilizado na transferência de informações. O mais comum é o uso de HTTP/SOAP, porém a tecnologia WCF suporta outros padrões como TCP (que é justamente o objeto de estudo deste artigo);
  • Contract: contratos que definem quais operações (métodos) são disponibilizadas para utilização do cliente/consumidor do serviço, bem como o formato dos prováveis parâmetros de cada uma destas e seus respectivos valores de retorno;
  • Behaviors: configurações usadas na customização de diversos tipos de comportamentos em um serviço WCF.

Conforme já mencionado, a escolha por um binding específico determinará qual o protocolo a ser utilizado na comunicação entre um serviço e prováveis consumidores do mesmo. Pelo fato de grande parte das aplicações WCF representarem Web Services voltados à integração de sistemas, BasicHttpBinding e WSHttpBinding costumam ser as alternativas naturais para suportar a transferência de informações via HTTP/SOAP.

BasicHttpBinding é um binding que foi criado por questões de compatibilidade, servindo basicamente para possibilitar a construção de serviços em conformidade com a especificação mais antiga de Web Services (WS-BasicProfile 1.1) suportada pela plataforma .NET. Já o binding WSHttpBinding é baseado em mecanismos de comunicação mais recentes, permitindo a utilização de um conjunto de padrões conhecidos como WS-*.

As especificações WS-* são o resultado de uma iniciativa conjunta entre gigantes da área de Tecnologia de Informação como Microsoft, Oracle e IBM, tendo por objetivo principal estabelecer uma série de convenções que assegurem a interoperabilidade entre sistemas elaborados nas mais diferentes plataformas. Um exemplo de especificação WS-* empregada em larga escala dentro de aplicações orientadas a serviços é o padrão WS-Security, o qual contempla em suas definições protocolos de segurança voltados à transmissão de dados confidenciais.

Haverá situações, no entanto, em que outros formatos além do padrão SOAP/HTTP podem se revelar como mais apropriados para a transmissão de informações. Cenários que envolvam a comunicação de aplicações .NET dentro de uma intranet podem se valer do uso do protocolo TCP, com esta opção oferecendo uma maior velocidade no tráfego de dados: a implementação de um serviço WCF em tais casos poderá empregar então o binding netTcpBinding.

Contudo, algumas ressalvas precisam ser feitas no que diz respeito à utilização do netTcpBinding. Por mais que esta alternativa possa significar um incremento na performance, a interoperabilidade acaba por ser comprometida: isto acontece porque os objetos transmitidos de um ponto a outro foram serializados em um padrão binário próprio da tecnologia WCF, impedindo assim que aplicações construídas em outras plataformas consigam consumir funcionalidades através do padrão TCP. Logo, o uso de WCF via TCP precisa ser analisado com cuidado, já que estará restrito a soluções concebidas a partir do .NET Framework.

Na próxima seção é apresentado um exemplo de solução baseada na utilização da tecnologia WCF, empregando-se para isto o padrão TCP como protocolo de comunicação (por meio do uso de netTcpBinding).

Criando o serviço WCF de exemplo

A solução que está sendo apresentada neste artigo foi criada no .NET framework 4.5, através da utilização do Microsoft Visual Studio 2012 Release Candidate. Basicamente, será construído um serviço para a gravação de informações de log, com este componente sendo consumido por outros sistemas elaborados em .NET que necessitem registrar informações como alertas, avisos de cunho geral ou, até mesmo, erros. Farão parte da Solution já citada os seguintes projetos:

  • TesteWCF_TCP.DataContracts: conterá a classe que representa um registro de log a ser gravado numa base de dados criada especificamente para este fim;
  • TesteWCF_TCP.Repository: projeto no qual será implementada a classe de acesso a dados, a qual será responsável por persistir em uma base de dados registros de log gerados por aplicações-cliente;
  • TesteWCF_TCP.Services: conterá a interface que define as operações do serviço a ser criado, bem como a classe de implementação correspondente (esta última será responsável ainda por invocar a camada de acesso a dados, além de contar com regras de negócios);
  • TesteWCF_TCP.Hosting: aplicação console por meio da qual o serviço será ativado para testes.

A Solution apresentada neste artigo pode ser considerada um exemplo de aplicação multicamadas. Do ponto de vista prático, o desenvolvimento em camadas consiste, primariamente, na criação de uma solução organizada em diversas partes lógicas (camadas). Este tipo de arquitetura busca, por meio deste tipo de divisão, a construção de aplicações de software melhor estruturadas e com um comportamento mais flexível diante da necessidade de mudanças, focando ainda na possibilidade de reutilização de alguns dos componentes que foram elaborados durante a implementação do projeto.

O tipo Log (Listagem 1) corresponde a um registro que representará um erro, aviso ou ainda alerta, com tal conjunto de dados sendo gravado em uma tabela de log acessível ao serviço WCF que se está construindo.

Listagem 1: Classe Log



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;

namespace TesteWCF_TCP.DataContracts
{
    [DataContract(Namespace = "http://TesteWCF_TCP/data/log")]
    public class Log
    {
        [DataMember(Order = 0)]
        public string Aplicacao { get; set; }

        [DataMember(Order = 1)]
        public string Mensagem { get; set; }

        public DateTime DataOcorrencia { get; set; }

        public string TipoOcorrencia { get; set; }
    }
}

Esta classe (Log) é um bom exemplo de um tipo de elemento conhecido dentro de WCF como Data Contract. Em termos gerais, um Data Contract nada mais é do que um tipo complexo, ou seja, uma estrutura composta pela combinação de tipos primitivos ou mesmo outras construções mais elaboradas (referências que apontem para outras classes). Data Contracts podem ser empregados tanto como parâmetros de entrada em operações de um serviço, quanto como valor a ser devolvido após a execução de tais métodos.

Para ser considerada um Data Contract, uma classe precisa ter associada à sua definição um atributo DataContractAttribute (namespace System.Runtime.Serialization). Este último é utilizado para fornecer instruções em modo runtime sobre como a serialização de um objeto ocorrerá.

É importante fazer ainda uma observação quanto ao uso do tipo DataContractAttribute: por convenção dentro da plataforma .NET, nomes de classes que representam atributos terminam com o sufixo Attribute. Instruções que envolvam um atributo vinculado a uma estrutura de código dispensam o uso de tal sufixo ao final do nome. Logo, ao se utilizar o atributo DataContractAttribute, emprega-se apenas “DataContract” na construção a ser marcada com este elemento.

Com a propriedade Namespace de DataContractAttribute é possível definir uma identificação única para um Data Contract: o valor configurado neste item pode ser uma URI (sigla do inglês "Uniform Resource Identification", padrão para definição de endereços na Internet) e, caso o mesmo não seja informado, assume-se http://tempuri.org como default. Trata-se de uma boa prática definir um valor para a propriedade Namespace, já que um identificador único para esta configuração torna possível distinguir um Data Contract de outros utilizados por uma aplicação.

Além da própria definição da classe, as propriedades de um Data Contract que serão expostas via serviço também deverão ter um atributo ligado às mesmas, fazendo-se uso neste caso do tipo DataMemberAttribute (pertencente ao namespace System.Runtime.Serialization). Através deste último é possível definir uma série de características, tais como se é obrigatória a existência de um valor para uma propriedade de um objeto que foi serializado dentro de uma mensagem, se estará sendo assumido o valor default da propriedade em questão quando nenhum valor tiver sido atribuído a esta, dentre outros aspectos.

Analisando a estrutura da classe DataContract deve-se destacar que, por padrão, membros de uma classe marcados com DataMember e que não tenham a propriedade Order definida são ordenados alfabeticamente e serializados, tomando como base para isto seus nomes correspondentes. Na sequência, elementos marcados com DataMember e com a propriedade Order configurada são serializados, respeitando-se a ordem numérica de tais itens.

As propriedades Aplicacao (nome da aplicação que originou o registro em log) e Mensagem (descrição do evento que o registro de log representa) foram marcadas com o atributo DataMemberAttribute. Isto aconteceu, justamente, devido ao fato de que tais propriedades deverão ser preenchidas (nos prováveis sistemas que consumiriam o serviço WCF que se está implementando aqui).

Já as propriedades DataOcorrencia (data/hora em que se cadastrou o registro de log) e TipoOcorrencia (indicação do tipo de informação representada por um registro: erro, alerta ou ainda, um evento qualquer dentro de uma aplicação-cliente) não foram marcadas com DataMemberAttribute. Tais propriedades são utilizadas apenas para efeitos de acesso à base de dados via ADO.NET, não precisando ser preenchidas por possíveis consumidores do serviço que está sendo implementado. Logo, não constarão em objetos serializados que irão constituir as mensagens enviadas ao serviço WCF de exemplo.

Por fim, um último ponto merece ser ressaltado a respeito do Data Contract definido dentro do projeto TesteWCF_TCP.DataContracts: o tipo Log foi implementado de maneira a permitir que fosse exposto tanto via serviço WCF, quanto utilizado para a manipulação de dados através das classes do ADO.NET. Com este artifício foi possível obter uma maior reusabilidade no que se refere ao uso da classe Log.

Na Listagem 2 está a definição da classe LogRepository. Esta última faz parte da Class Library TesteWCF_TCP.Repository, sendo responsável pela gravação de registros de log em uma tabela de nome TB_LOG (a estrutura correspondente é apresentada na Listagem 3). A fim de cumprir tal objetivo, o tipo LogRepository emprega dentro do método IncluirRegistroLog recursos disponibilizados pelo ADO.NET:

  • A partir da variável “conexão” (instância do tipo SqlConnection) é estabelecida uma conexão com a base de dados em que ocorre a persistência das informações (empregando para isto uma string de conexão chamada “TesteWCF_TCP”);
  • A referência de nome “comando” (objeto do tipo SqlCommand) representa a instrução SQL que fará a inclusão de um registro no banco de dados;
  • Os dados para a execução do comando INSERT são obtidos a partir da referência “log”, a qual foi informada como parâmetro ao se invocar a operação IncluirRegistroLog;
  • As chamadas ao método Add da propriedade Parameters de “comando” permitem associar os valores do objeto “log” aos parâmetros equivalentes do comando de inclusão.

Listagem 2: Classe LogRepository


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using TesteWCF_TCP.DataContracts;

namespace TesteWCF_TCP.Repository
{
    public class LogRepository
    {
        public void IncluirRegistroLog(Log log)
        {
            using (SqlConnection conexao = new SqlConnection(
                ConfigurationManager.ConnectionStrings["TesteWCF_TCP"]
                .ConnectionString))
            {
                SqlCommand cmd = conexao.CreateCommand();
                cmd.CommandText =
                    @"INSERT INTO dbo.TB_LOG
                               (Aplicacao
                               ,Mensagem
                               ,DataOcorrencia
                               ,TipoOcorrencia)
                         VALUES
                               (@APLICACAO
                               ,@MENSAGEM
                               ,@DATAOCORRENCIA
                               ,@TIPOOCORRENCIA)";
                cmd.Parameters.Add("@APLICACAO",
                    SqlDbType.VarChar).Value = log.Aplicacao.Trim();
                cmd.Parameters.Add("@MENSAGEM",
                    SqlDbType.VarChar).Value = log.Mensagem.Trim();
                cmd.Parameters.Add("@DATAOCORRENCIA",
                    SqlDbType.DateTime).Value = log.DataOcorrencia;
                cmd.Parameters.Add("@TIPOOCORRENCIA",
                    SqlDbType.Char).Value = log.TipoOcorrencia;
                    
                conexao.Open();
                cmd.ExecuteNonQuery();
                conexao.Close();
            }
        }
    }
}

Listagem 3: Estrutura da tabela TB_LOG


CREATE TABLE [dbo].[TB_LOG](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[Aplicacao] [varchar](50) NOT NULL,
	[Mensagem] [varchar](2000) NOT NULL,
	[DataOcorrencia] [datetime] NOT NULL,
	[TipoOcorrencia] [char](3) NOT NULL,
	CONSTRAINT [PK_TB_LOG] PRIMARY KEY ([Id]),
	CONSTRAINT [CK_TB_LOG_TIPO_OCORRENCIA]
		CHECK ([TipoOcorrencia] IN ('ERR', 'ALT', 'EVT'))
)

TesteWCF_TCP.Services é um projeto do tipo Class Library no qual estão definidos:

  • A interface ILogUtilityService, a qual corresponde ao contrato que define o serviço de exemplo;
  • A classe LogUtilityService que implementa as funcionalidades oferecidas pelo serviço de exemplo.

Na interface ILogUtilityService (Listagem 4) estão declaradas as operações que serão expostas através do serviço WCF abordado neste artigo. ILogUtilityService foi marcada com o atributo ServiceContractAttribute: este é um pré-requisito para se expor uma interface (ou mesmo uma classe) como um serviço quando se emprega a tecnologia WCF. Além disso, toda operação passível de ser invocada por um provável consumidor do serviço deverá ser marcada com o atributo OperationContractAttribute.

Ainda sobre o atributo a utilização do atributo ServiceContractAttribute, a propriedade Namespace foi preenchida com o intuito de se atribuir uma identificação única ao serviço. Trata-se do mesmo procedimento usado anteriormente na definição do Data Contract que representa um registro de log.

Por mais que a tecnologia WCF não exija que se utilize uma interface para definir um serviço, o uso de tais estruturas representa uma boa prática, trazendo ainda consigo uma série de benefícios.

Uma simples classe pode se basear em diversas interfaces, as quais se referem a diferentes contratos de serviços. Além disso, a implementação de um serviço pode ser alterada normalmente, sem causar efeitos colaterais aos consumidores do mesmo. Isso é possível em virtude do desenvolvimento ter sido orientado a interfaces. É possível ainda definir mecanismos para versionamento de um serviço, empregando para isto uma interface para cada versão que estará disponível.

Listagem 4: Interface ILogUtilityService


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
using TesteWCF_TCP.DataContracts;

namespace TesteWCF_TCP.Services
{
    [ServiceContract(Namespace = "http://TesteWCF_TCP/services/log/")]
    public interface ILogUtilityService
    {
        [OperationContract]
        void RegistrarErro(Log log);
        
        [OperationContract]
        void RegistrarAlerta(Log log);
	
        [OperationContract]
        void RegistrarEvento(Log log);
    }
}

A classe LogUtilityService (Listagem 5) corresponde à implementação do serviço WCF de exemplo. Todos os métodos definidos na interface IConsultaPendenciasService são implementados por este tipo, sendo que os mesmos estarão disponíveis para utilização tão logo o serviço se encontrar em execução.

Ao se considerar a forma como o tipo LogUtilityService foi estruturado, merece destaque ainda o fato de que este serviço representa um caso de aplicação prática de um pattern SOA conhecido como Utility Service (http://www.soaglossary.com/utility_service.php).

O padrão Utility Service é utilizado na construção de serviços responsáveis pela disponibilização de funções genéricas, não relacionadas diretamente a regras de negócio de um sistema. Bons exemplos disto são componentes que encapsulam mecanismos de log, envio de notificações por e-mail, dentre outras funcionalidades que podem vir a ser consumidas por mais de uma aplicação-cliente.

Listagem 5: Classe LogUtilityService


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
using TesteWCF_TCP.DataContracts;
using TesteWCF_TCP.Repository;

namespace TesteWCF_TCP.Services
{
    [ServiceBehavior(Namespace = "http://TesteWCF_TCP/services/log/")]
    public class LogUtilityService : ILogUtilityService
    {
        private void ProcessarRegistro(Log log, string tipoOcorrencia)
        {
            if (String.IsNullOrWhiteSpace(log.Aplicacao))
            {
                throw new FaultException(
                    "É obrigatório informar o nome da aplicação.");
            }

            if (String.IsNullOrWhiteSpace(log.Mensagem))
            {
                throw new FaultException(
                    "É obrigatório informar a mensagem de log.");
            }

            log.DataOcorrencia = DateTime.Now;
            log.TipoOcorrencia = tipoOcorrencia;
            new LogRepository().IncluirRegistroLog(log);
        }

        public void RegistrarErro(Log log)
        {
            ProcessarRegistro(log, "ERR");
        }

        public void RegistrarAlerta(Log log)
        {
            ProcessarRegistro(log, "ALT");
        }

        public void RegistrarEvento(Log log)
        {
            ProcessarRegistro(log, "EVT");
        }
    }
}

No código-fonte em que está a implementação do tipo LogUtilityService é possível notar:

  • O uso do atributo ServiceBehaviorAttribute. Esta construção permite especificar comportamentos para um serviço, sendo que neste último caso foi configurado o namespace que identifica o mesmo (através da propriedade também chamada Namespace);
  • As operações RegistrarErro, RegistrarAlerta e RegistrarEvento recebem como parâmetro uma instância da classe Log, acionando quando invocadas o método privado ProcessarRegistro. A este último são repassados como parâmetros o objeto do tipo Log, bem como o tipo de ocorrência que pode ser um erro (“ERR”), um alerta (“ALT”) ou ainda, um evento genérico (“EVT”);
  • Quanto à codificação do método ProcessarRegistro, o mesmo verifica se o nome da aplicação que originou a mensagem e o conteúdo desta estão preenchidos; em caso negativo, uma exceção é lançada através de uma instância da classe FaultException (tipo de exceção comumente empregado em aplicações WCF). Além disso, a operação ProcessarRegistro acessa a camada de dados a fim de incluir um novo registro, sendo que este procedimento acontece através da invocação do método IncluirRegistroLog a partir de uma instância do tipo LogRepository.

Uma última observação precisa ser feita a respeito de ProcessarRegistro: por mais que esta método fosse público, o mesmo não seria exposto aos consumidores do serviço, uma vez que não foi marcado com o atributo OperationContractAttribute.

O serviço a partir do qual serão testados os conceitos aqui abordados será acionado através da Console Application TesteWCF_TCP.Hosting.

A Listagem 6 apresenta o arquivo app.config, o qual contém as configurações que permitem que o serviço WCF seja acessado via padrão TCP.

Listagem 6: Arquivo app.config da aplicação TesteWCF_TCP.Hosting


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="TesteWCF_TCP"
         providerName="System.Data.SqlClient"
         connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=TesteWCF_TCP;Integrated Security=True;MultipleActiveResultSets=True;"/>
  </connectionStrings>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="TesteWCF_TCP.LogUtilityServiceBehavior"
        name="TesteWCF_TCP.Services.LogUtilityService">
        <endpoint address="" binding="netTcpBinding" 
          bindingConfiguration=""
          contract="TesteWCF_TCP.Services.ILogUtilityService">
        </endpoint>
        <endpoint address="mex" binding="mexTcpBinding" 
          bindingConfiguration=""
          contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:7123/LogUtilityService" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="TesteWCF_TCP.LogUtilityServiceBehavior">
          <serviceMetadata httpGetEnabled="false" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
</configuration>

A ConnectionString TesteWCF_TCP definida no arquivo de configuração aponta para o banco de dados utilizado pela solução aqui demonstrada, servindo de base para a gravação de informações de log a partir da camada de acesso a dados.

A configuração de um serviço acontece dentro de um elemento chamado "service", o qual pertence ao agrupamento "services" da seção "system.serviceModel".

Dentro do elemento baseAddresses foi definido um endereço-base para o acesso ao serviço, já considerando inclusive que o processo de comunicação acontecerá através do protocolo TCP/IP (o endereço em questão é iniciado com “net.tcp”).

Em seguida, especificou-se o computador que atua como servidor (por ser um teste, foi utilizado o computador local, ou seja, “localhost”): tanto o nome quanto um endereço de IP são válidos nestas situações. Além disso, é obrigatório indicar ainda a porta em que se estabelecerá a comunicação (“7123” era uma porta disponível no momento em que construí esta aplicação, sendo que isto pode variar de um computador para outro).

O primeiro endpoint definido neste exemplo não teve seu atributo address preenchido. Por convenção, assume-se que este será o endpoint default para acesso ao serviço WCF. Além disso, foi definido o uso do binding netTcpBinding, garantindo com isso que a comunicação entre a solução WCF e aplicações-cliente aconteça via protocolo TCP.

Há ainda um segundo endpoint que foi criado para esta aplicação de exemplo, com tal item utilizando a interface IMetadataExchange (namespace System.ServiceModel.Description) como contrato. Essa referência emprega o binding mexTcpBinding, retornando informações de metadata relativas ao serviço em questão: trata-se de uma prática em conformidade com a especificação WS-MetadataExchange (WS-MEX), a qual estabelece regras para que aplicações consumidoras consigam determinar a estrutura de um serviço remoto. O endereço deste endpoint será obtido concatenando-se a string indicada em “baseAddresses”, juntamente com o valor “mex” (valor este que foi especificado no atributo address do elemento endpoint).

Já o agrupamento “behaviors” representa o ponto em que são especificados, basicamente, os diferentes comportamentos possíveis para os serviços de uma aplicação: cada um destes componentes contará, normalmente, com um elemento de nome “behavior”.

O item “behavior” (cuja identificação consta no atributo “name”) corresponde ao comportamento indicado no atributo "behaviorConfiguration" do elemento “servisse”. Sobre “behavior”, é importante frisar os seguintes aspectos:

  • O atributo httpGetEnabled de serviceMetadata marcado como true indica que consumidores poderão obter o WSDL do Web Service via requisições HTTP do tipo GET. Não é este o caso, uma vez que o serviço implementado se baseia no protocolo TCP. O padrão WSDL (Web Services Description Language) é utilizado para expor os diferentes contratos de um Web Service em um formato padronizado, utilizando para isto um endereço específico;
  • O atributo “includeExceptionDetailInFaults” do elemento “serviceDebug” define se detalhes de exceções serão retornados para consumidores do serviço. Para efeitos de depuração, este item pode ser configurado como “true” durante o desenvolvimento; no entanto, recomenda-se que não se adote tal comportamento para serviços WCF em ambiente de produção.

Apesar de grande parte da configuração do serviço de log ter sido feita em um arquivo XML, a tecnologia WCF também oferece suporte para se efetuar estes mesmos procedimentos no código-fonte. Como sempre, a escolha da melhor alternativa dependerá do contexto e de requisitos que são específicos de cada projeto.

A definição de configurações em um arquivo XML apresenta, em muitos casos, uma série de vantagens. Isto é mais do que perceptível ao se considerar a flexibilidade em se alterar apenas um arquivo, descartando com isto a necessidade de recompilar toda uma aplicação.

Na Listagem 7 está definida a classe Program, por meio da qual o serviço para gravação de informações em log será iniciado. Através da criação de uma instância da classe ServiceHost e da invocação do método Open sobre a mesma, o Web Service será ativado. Este processo buscou simplificar o procedimento de inicialização do serviço para testes, dispensando-se assim a necessidade de publicação do mesmo num servidor IIS (Internet Information Services) ou, ainda, sua implantação a partir de um Windows Service.

Listagem 7: Classe Program


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
using TesteWCF_TCP.Services;

namespace TesteWCF_TCP.Hosting
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ServiceHost host =
                new ServiceHost(typeof(LogUtilityService)))
            {
                host.Open();

                Console.WriteLine(
                    "Serviço iniciado com sucesso!");
                Console.WriteLine(
                    "Pressione qualquer tecla para encerrar a aplicação...");
                Console.ReadKey();

                host.Close();
            }
        }
    }
}

Ao término da implementação das diferentes estruturas aqui apresentadas, a aplicação de exemplo possuirá uma estrutura bastante similar à da solução da Figura 1.

Solution ao término da implementação dos diferentes projetos

Figura 1: Solution ao término da implementação dos diferentes projetos

Executando a aplicação TesteWCF_TCP.Hosting por meio do Visual Studio, o Firewall do Windows poderá exibir um alerta semelhante ao que consta na Figura 2. Se tal mensagem realmente aparecer em tela, faz-se necessário então selecionar a opção “Permitir Acesso”.

Liberando o acesso à aplicação WCF via Firewall do Windows

Figura 2: Liberando o acesso à aplicação WCF via Firewall do Windows

Na sequência a isto, será exibida uma tela idêntica à que consta na Figura 3. Enquanto esta janela permanecer aberta, o serviço em questão estará disponível para utilização.

Aplicação WCF em execução

Figura 3: Aplicação WCF em execução

Consumindo o serviço de exemplo

Para se testar a aplicação WCF implementada na seção anterior, será necessário primeiramente criar uma referência para o serviço de testes chamada TesteWCF_TCP. Dentro do Visual Studio, isto pode ser feito a partir da janela Solution Explorer, devendo-se clicar com o botão direito do mouse sobre o item "Service References" de um projeto e selecionando a opção "Add Service Reference..." (conforme indicado na Figura 4).

Adicionando uma referência a um serviço num projeto de testes

Figura 4: Adicionando uma referência a um serviço num projeto de testes

Neste momento aparecerá uma tela como a que consta na Figura 5. Informar no campo “Address” o seguinte endereço (considerando neste caso o endpoint baseado no binding mexTcpBinding): net.tcp://localhost:7123/LogUtilityService/mex

Já no campo “Namespace” deve ser especificado o namespace a que pertencerão os diferentes tipos que constituem a referência que está adicionando ao projeto (preencher neste caso com o valor “TesteWCF_TCP”).

Criando o arquivo para implementação da classe IMCException

Figura 5: Criando o arquivo para implementação da classe IMCException

Com a referência ao serviço criada, já é possível acessar o mecanismo de log via TCP. A Listagem 8 apresenta um exemplo disto, em que dentro do módulo Main que inicializa uma aplicação .NET foi invocada a operação RegistrarEvento da solução WCF aqui demonstrada.

Listagem 8: Consumindo o service WCF


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TesteConsumoWCF_TCP.TesteWCF_TCP;

namespace TesteConsumoWCF_TCP
{
    class Program
    {
        static void Main(string[] args)
        {
            using (LogUtilityServiceClient client =
                new LogUtilityServiceClient())
            {
                Log log = new Log();
                log.Aplicacao = "TesteConsumoWCF_TCP.exe";
                log.Mensagem = "Carregando a aplicação";
                client.RegistrarErro(log);
            }

            // Outras operações inicializando a aplicação...
            ...

        }
    }
}

Conclusão

Muitos profissionais cometem um equívoco ao pressupor que a implementação de serviços WCF está restrita ao uso do padrão SOAP. Por mais que muitos projetos utilizando esta tecnologia resultem na construção de Web Services baseados em XML, outros protocolos de comunicação podem perfeitamente ser empregados em situações, viabilizando assim a integração entre diferentes sistemas.

Este é justamente o caso do padrão TCP, opção ideal para cenários em que ocorrerá o intercâmbio de informações apenas entre aplicações .NET. Quando isto acontecer, o binding netTcpBinding e a serialização binária de objetos podem se revelar como alternativas de melhor performance em uma rede interna, sobretudo se comparados com uma construção equivalente que dependa da combinação SOAP/HTTP.

Procurei com este artigo demonstrar novas possibilidades para a tecnologia WCF. Espero que o conteúdo aqui abordado possa auxiliá-lo em algum momento. Até uma próxima oportunidade!