|
Faça o download do arquivo.
A Microsoft disponibiliza várias tecnologias para o desenvolvimento de aplicações distribuídas. Cada uma delas é voltada para uma necessidade específica, e entre essas tecnologias temos: ASP.NET Web Services, WSE - Web Services Enhancements, .NET Remoting, COM+ - Enterprise Services e MSMQ - Message Queue. Cada uma delas possui sua própria API, com vários tipos que devem ser estudados para que possamos desenvolver uma aplicação que exponha ou consuma recursos destas tecnologias.
Quando iniciou a criação do .NET Framework 3.0, um entre quatro dos grandes pilares que havia dentro dele era o Indigo que mais tarde recebeu o nome de Windows Communication Foundation, ou simplesmente WCF. O WCF unificou as várias tecnologias de programação distribuídas na plataforma Microsoft em um único modelo, baseando-se na arquitetura orientada à serviços (SOA). Essa nova API facilita consideravelmente o aprendizado e desenvolvimento, já que o WCF está totalmente desacoplado das regras de negócios que serão expostas pelo serviço. A finalidade deste artigo é mostrar uma introdução ao WCF, construindo passo-à-passo um exemplo simples de como criar e consumir um serviço.
Para começar a fazer uso do WCF, tudo o que precisamos é referenciar em nossa aplicação o assembly System.ServiceModel.dll. Esse assembly possui a maioria dos tipos necessários para a construção de um serviço ou de um cliente. Ainda há outros assemblies que complementam o WCF, como é o caso do suporte à serviços baseados em REST, mas que serão abordados em artigos específicos.
Estrutura
A estrutura de um serviço WCF não é muito complexa, pois devemos utilizar conceitos puros de programação .NET para a criação do contrato e da classe que representará o serviço. Além disso, o WCF também suporta a utilização de tipos complexos, como classes que criamos para atender uma determinada necessidade.
O primeiro passo na criação do serviço é a definição do contrato. É o contrato que determinará quais operações estarão expostas, quais informações essas operações necessitam para ser executadas e também qual será o tipo de retorno. O contrato nada mais é que uma Interface que, por sua vez, deverá possuir os métodos (apenas a sua assinatura) que serão expostos. A Interface que servirá como contrato deverá ser obrigatoriamente decorada com o atributo ServiceContractAttribute pois, caso contrário, uma exceção do tipo InvalidOperationException será disparada antes da abertura do host.
Nem sempre todos os membros expostos pela Interface devem ser expostos para o serviço e, justamente por isso, todas as operações que serão disponibilizadas devem ser decoradas com o atributo OperationContractAttribute. Vale lembrar que o WCF obriga a termos no mínimo uma operação definida com este atributo, já que não faz sentido publicar um serviço que não tenha nenhuma operação a ser executada. Caso a Interface não possua nenhuma operação definida com este atributo, uma exceção do tipo InvalidOperationException também será disparada antes da abertura do host. O código abaixo exibe uma Interface simples, que servirá como exemplo para o artigo:
using System;
using System.ServiceModel;
[ServiceContract]
public interface IContrato
{
[OperationContract]
Usuario RecuperarUsuario(string nome);
}
Imports System
Imports System.ServiceModel
<ServiceContract()> _
Public Interface IContrato
<OperationContract()> _
Function RecuperarUsuario(ByVal nome As String) As Usuario
End Interface
|
|
C#
|
VB.NET
|
|
Para fins de exemplo, esta Interface apenas terá um único membro, mas ela poderá conter vários outros e, como já dito acima, você controla a visibilidade destes membros através do atributo OperationContractAttribute. Como podemos notar, o método RecuperarUsuario retorna uma instância da classe Usuario. Neste momento dois novos atributos entram em cena: DataContractAttribute e DataMemberAttribute, ambos contidos no namespace System.Runtime.Serialization, fornecido pelo assembly System.Runtime.Serialization.dll.
Os data contracts são uma forma que se tem de publicar possíveis estruturas de dados que podem ser trocadas durante o envio e recebimento de uma mensagem. A utilização do atributo DataContractAttribute determina que uma classe poderá ser exposta através de um serviço WCF, e deve ser aplicado a todas as classes que estão referenciadas, como parâmetro ou tipo de retorno, em um contrato (Interface). Já os tipos primitivos, como String, DateTime, Int32, não precisam disso, já que podem ser serializados diretamente.
Já o atributo DataMemberAttribute deve ser aplicado nos campos e propriedades que o tipo possui e que devem ser expostos através do serviço. Esse atributo irá controlar a visibilidade do campo ou da propriedade para os clientes que consomem o serviço, não importando o modificador de acesso (public, private, etc.) que possui. O código abaixo define a classe Usuario:
using System;
using System.Runtime.Serialization;
[DataContract] //Opcional com .NET 3.5 + SP1
public class Usuario
{
[DataMember] //Opcional com .NET 3.5 + SP1
public string Nome { get; set; }
}
Imports System
Imports System.Runtime.Serialization
<DataContract()> _ 'Opcional com .NET 3.5 + SP1
Public Class Usuario
Private _nome As String
<DataMember()> _ 'Opcional com .NET 3.5 + SP1
Public Property Nome() As String
Get
Return Me._nome
End Get
Set(ByVal value As String)
Me._nome = value
End Set
End Property
End Class
|
|
C#
|
VB.NET
|
|
Observação: A partir do Service Pack 1 do .NET Framework 3.5 esse comportamento foi mudado. Visando o suporte ao POCO (Plain Old C# Objects), a Microsoft tornou mais flexível a utilização de data contracts em serviços WCF, não obrigando às classes, propriedades e campos serem decorados com os atributos acima citados. Com isso, apenas propriedades do tipo escrita/leitura serão serializadas. A partir do momento que você decora a classe com o atributo DataContractAttribute, você também deverá especificar, via DataMemberAttribute, quais campos deverão ser serializados.
Vale lembrar também que o atributo XmlSerializableAttribute (namespace System.Xml.Serialization) e as Interfaces IXmlSerializable (namespace System.Xml.Serialization) e ISerializable (namespace System.Runtime.Serialization) também continuam sendo suportadas, permitindo que você customize como o objeto será serializados/deserializados pelo WCF ou qualquer outro recurso fornecido .NET Framework.
Uma vez que o contrato do serviço esteja definido e os possíveis tipos que ele expõe também estejam devidamente configurados, o próximo passo é a criação da classe que representa o serviço. Esta classe deverá implementar todos os membros expostos pela Interface que define o contrato do serviço, inclusive aqueles que não estão marcados com o atributo OperationContractAttribute, lembrando que a implementação de uma Interface em uma classe é uma imposição da linguagem, e não do WCF.
A implementação dos métodos poderá conter a própria regra de negócio, bem como pode servir de wrapper para algum outro componente ou serviço. Além disso, as classes que representam o serviço também podem configurar alguns outros recursos fornecidos pelo WCF e que estão acessíveis através de behaviors, como por exemplo: transações, sessões, segurança, etc., mas veremos isso mais detalhadamente abaixo. O WCF desacopla totalmente a regra do negócio de sua API e, justamente por isso, que é possível notar no código abaixo que a classe que representa o serviço não possui nenhuma configuração do WCF:
using System;
public class Servico : IContrato
{
public Usuario RecuperarUsuario(string nome)
{
return new Usuario() { Nome = nome };
}
}
Imports System
Public Class Servico
Implements IContrato
Public Function RecuperarUsuario(ByVal nome As String) As Usuario _
Implements IContrato.RecuperarUsuario
Return New Usuario() With {.Nome = nome}
End Function
End Class
|
|
C#
|
VB.NET
|
|
Por si só esta classe não trabalha, pois deverá ser consumida pelo WCF. Mas afinal, como se determina que é esta classe responsável por atender as requisições? Isso é realizado através do host, ou melhor, da classe ServiceHost. Logo no construtor desta classe, você deverá passar uma instância da classe Type, apontando para a classe que representa o serviço e, obrigatoriamente, deverá implementar todos os possíveis contratos que são expostos através dos endpoints. A configuração do host para este exemplo será abordada na seção seguinte.
Grande parte dos atributos que vimos nesta seção disponibilizam várias propriedades que nos permitem interagir com o serializador/deserializador da mensagem e, além disso, permitem especificarmos algumas regras que serão validadas antes da abertura do host e, caso não sejam atendidas, uma exceção será disparada. Como essas propriedades influenciam nas mais variadas funcionalidades expostas pelo WCF, elas serão detalhadamente abordadas nos artigos que correspondem à sua utilização. Para conhecer os artigos disponíveis, consulte a listagem na seção Explorando outras Funcionalidades.
Hosting
Uma das grandiosidades do WCF é a possibilidade de utilizar qualquer tipo de aplicação como host, ou seja, ele não tem uma dependência de algum software, como o IIS (Internet Information Services), como acontecia com os ASP.NET Web Services. O WCF pode expor serviços para serem acessados através dos mais diversos tipos de protocolos, como por exemplo: HTTP, TCP, IPC e MSMQ.
Atualmente temos três alternativas de hosting: self-hosting, IIS e o WPAS. Como há vários detalhes na criação e gerenciamento do hosting, ficaria muito extenso publicar cada detalhe, vantagens e desvantagens que cada uma das técnicas possui. Para maiores detalhes, consulte este artigo que explora cada uma das funcionalidades expostas pelo WCF para a interação com o hosting.
O host é representado dentro do WCF pela classe ServiceHost ou uma de suas variações e, é através dela que efetuamos várias configurações, como endpoints, segurança, etc. Em seu construtor, ela espera a classe que representa o serviço, podendo ser definida através de seu tipo (classe Type) ou através de uma instância desta mesma classe anteriormente criada (Singleton). Para o exemplo utilizado neste artigo, a configuração parcial do host fica da seguinte forma:
using System;
using System.ServiceModel;
using (ServiceHost host = new ServiceHost(typeof(Servico),
new Uri[] { new Uri("net.tcp://localhost:9393") }))
{
//endpoints
host.Open();
Console.ReadLine();
}
Imports System
Imports System.ServiceModel
Using host As New ServiceHost(GetType(Servico), _
New Uri() {New Uri("net.tcp://localhost:9393")})
'endpoints
host.Open()
Console.ReadLine()
End Using
|
|
C#
|
VB.NET
|
|
Endpoints
Os endpoints são uma das características mais importantes de um serviço, pois é por onde toda a comunicação é realizada, pois fornece o acesso aos clientes do serviço WCF que está sendo disponibilizado. Para compor um endpoint, basicamente precisamos definir três propriedades que obrigatoriamente precisamos para poder trabalhar: address (A), binding (B) e contract (C) e, opcionalmente, definir alguns behaviors, que falaremos na sequência. A figura abaixo ilustra a estrutura dos endpoints e onde eles estão situados:
 |
| Figura 1 - Estrutura de um endpoint. |
O address consiste em definir um endereço único que permitirá aos clientes saber onde o serviço está publicado. O endereço geralmente é definido através de uma instância da classe Uri. Essa classe fornece um construtor que recebe uma string, contendo o protocolo, o servidor, a porta e o endereço do serviço (usado para diferenciar entre muitos serviços no mesmo local), tendo a seguinte forma: scheme://host[:port]/[path]. Cada uma dessas configurações são representadas respectivamente pelas seguintes propriedades da classe Uri: Scheme, Host, Port e AbsolutePath.
O protocolo indica sob qual dos protocolos suportados pelo WCF o serviço será exposto. Atualmente temos os seguintes protocolos: HTTP (http://), TCP (net.tcp://), IPC (net.pipe://) e MSMQ (net.msmq://). O host refere-se à máquina onde o serviço irá ser executado, podendo inclusive referenciar o localhost. A porta permite especificarmos uma porta diferente do valor padrão e, quando omitida, ela sempre assumirá a porta padrão especificada pelo protocolo. E, finalmente, temos o path, que é utilizado quando desejamos diferenciar entre vários serviços expostos sob um mesmo protocolo, host e porta.
O binding indica como a comunicação será realizada com aquele endpoint, como por exemplo, qual transporte será utilizado (HTTP, TCP, etc), qual a codificação utilizada (Binary ou Text) para serializar a mensagem, segurança, suporte à transações, etc. O WCF disponibiliza vários bindings, e através da tabela abaixo podemos analisar as características de cada um deles (sendo as opções em negrito a configuração padrão):
|
Binding
|
Características
|
|
BasicHttpBinding
|
Transporte: HTTP.
Segurança: None, Transport, Message e Mixed.
Suporte à Transações: Não.
Duplex: Não.
Sessions: Não.
Encoding: Text.
Modos de Transferência: Streaming e Buffered.
|
|
WebHttpBinding
|
Transporte: HTTP.
Segurança: None e Transport.
Suporte à Transações: Não.
Duplex: Não.
Sessions: Não.
Encoding: Text e MTOM.
Modos de Transferência: Streaming e Buffered.
|
|
WSHttpBinding
|
Transporte: HTTP.
Segurança: None, Transport, Message e Mixed.
Suporte à Transações: Sim.
Duplex: Sim.
Sessions: Sim.
Encoding: Text e MTOM.
Modos de Transferência: Buffered.
|
|
WSDualHttpBinding
|
Transporte: HTTP.
Segurança: None, Message e Mixed.
Suporte à Transações: Sim.
Duplex: Sim.
Sessions: Sim.
Encoding: Text e MTOM.
Modos de Transferência: Buffered.
|
|
WSFederationHttpBinding
|
Transporte: HTTP.
Segurança: None e Message.
Suporte à Transações: Sim.
Duplex: Não.
Sessions: Sim.
Encoding: Text e MTOM.
Modos de Transferência: Buffered.
|
|
NetTcpBinding
|
Transporte: TCP.
Segurança: None, Transport, Message e Mixed.
Suporte à Transações: Sim.
Duplex: Sim.
Sessions: Sim.
Encoding: Binary.
Modos de Transferência: Streaming e Buffered.
|
|
NetPeerTcpBinding
|
|
|
NetNamedPipeBinding
|
Transporte: IPC.
Segurança: None e Transport.
Suporte à Transações: Não.
Duplex: Sim.
Sessions: Sim.
Encoding: Binary.
Modos de Transferência: Streaming e Buffered.
|
|
NetMsmqBinding
|
Transporte: MSMQ.
Segurança: None, Transport e Message.
Suporte à Transações: Sim.
Duplex: Não.
Sessions: Sim.
Encoding: Binary.
Modos de Transferência: Buffered.
|
|
MsmqIntegrationBinding
|
|
|
Observação: Além da lista de bindings que vimos acima, temos ainda a classe CustomBinding que, como o próprio nome indica, possibilita a criação de um binding customizado, definindo qual o meio de transporte, codificação, suporte ou não à transações, etc.
Finalmente, a última característica de um endpoint é o contrato. Como já vimos acima, o contrato é representado por uma Interface e, uma vez que ele é definido como um contrato de serviço, são esses membros que serão disponibilizados aos clientes em forma de operações, definindo os parâmetros de entrada e o tipo de retorno e o formato da mensagem (request-reply, one-way ou duplex).
O que diferencia uma Interface normal de uma Interface que será utilizada como contrato de serviço. Os atributos que devem ser decorados na mesma (ServiceContractAttribute) e também naqueles membros que farão parte do serviço (OperationContractAttribute), devendo também se atentar aos tipos que são expostos, pois tipos complexos também devem ser marcados com um atributo especial (DataContractAttribute), e as propriedades que ele irá expor devem ser decoradas com o atributo DataMemberAttribute, tudo como já foi explicado acima.
|