Ao implementar Web Services como forma de expor serviços, devemos levar em consideração o princípio de SOA que recomenda haver um contrato formal para o serviço. Aliado a esse princípio, é recomendado utilizar a abordagem de Contract-First, ou seja, criar de antemão o WSDL e XSD do Web Service como forma de manter o contrato “enxuto”, ignorante de plataforma (Java, .NET, etc) e utilizando estruturas de dados padronizadas (centralized data models).

Tendo escrito o WSDL e XSD, ou seja, definido o contrato do serviço então sim implementa-se a lógica do serviço para atender a esse contrato.

A abordagem inversa ao Contract-First é denominada de Contract-Last, onde escrevemos primeiro a interface (C#) e classe do serviço e depois deixamos o framework (WCF) gerar o WSDL automaticamente. Essa abordagem é muito comum ao desenvolver com WCF pela sua facilidade e comodidade, apesar de não estar alinhado à recomendação de SOA.

WCF 4.5

Pensando neste cenário, a versão 4.5 do .NET Framework trouxe um recurso que permite facilitar a abordagem de Contract-First através do parâmetro /serviceContract do svcutil.exe. Este parâmetro permite gerar o código-fonte da interface do serviço e as classes de dados e mensagens a partir de arquivos de contrato (WSDL + XSD).

Os passos descritos a seguir exemplificam a criação de um serviço com WCF e Contract-First.

Passo 1 – Definir o Contrato (primeiro!)

Como dito, a primeira coisa a ser definida é o contrato, então o primeiro passo é escrever o WSDL e o XSD do serviço.
A título de demonstração será criado um serviço para fornecer o resultado do sorteio da loteria Megasena. Sendo assim, o contrato descrito no WSDL especifica, dentre outras coisas, um serviço com uma operação ObterResultado e os parâmetros de entrada e de saída.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions name="Megasena"
targetNamespace="http://service.rafaelleonhardt.com.br/soacontractfirstcomwcf45/megasena/v1" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://service.rafaelleonhardt.com.br/soacontractfirstcomwcf45/megasena/v1" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsd1="http://data.rafaelleonhardt.com.br/soacontractfirstcomwcf45/megasena/v1">
<wsdl:import
namespace="http://data.rafaelleonhardt.com.br/soacontractfirstcomwcf45/megasena/v1"
location="megasena.xsd"></wsdl:import>
<wsdl:types>
<xsd:schema targetNamespace="http://service.rafaelleonhardt.com.br/soacontractfirstcomwcf45/megasena/v1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" >
<xsd:import schemaLocation="megasena.xsd" namespace="http://data.rafaelleonhardt.com.br/soacontractfirstcomwcf45/megasena/v1"></xsd:import>
<xsd:complexType name="ConcursoRequest">
<xsd:sequence>
<xsd:element name="Numero" type="xsd:int" minOccurs="1" maxOccurs="1"></xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ConcursoResponse">
<xsd:sequence>
<xsd:element name="Concurso" type="xsd1:Concurso" minOccurs="1" maxOccurs="1"></xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="ConcursoRequest" type="tns:ConcursoRequest"></xsd:element>
<xsd:element name="ConcursoResponse" type="tns:ConcursoResponse"></xsd:element>
</xsd:schema>
</wsdl:types>
<wsdl:message name="ConcursoRequest">
<wsdl:part name="parameters" element="tns:ConcursoRequest">
</wsdl:part>
</wsdl:message>
<wsdl:message name="ConcursoResponse">
<wsdl:part name="parameters" element="tns:ConcursoResponse">
</wsdl:part>
</wsdl:message>
<wsdl:portType name="MegasenaService">
<wsdl:operation name="ObterConcurso">
<wsdl:input message="tns:ConcursoRequest" name="ConcursoRequest" ></wsdl:input>
<wsdl:output message="tns:ConcursoResponse" name="ConcursoResponse"></wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="MegasenaSOAP" type="tns:MegasenaService">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="ObterConcurso">
<soap:operation soapAction="http://service.rafaelleonhardt.com.br/soacontractfirstcomwcf45/megasena/v1/ObterConcurso" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="MegasenaService">
<wsdl:port binding="tns:MegasenaSOAP" name="MegasenaSOAP">
<soap:address location="http://localhost:39976/Megasena.svc" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
view raw megasena.wsdl hosted with ❤ by GitHub

É possível verificar no WSDL que há um Import (megasena.xsd), ou seja, há definições especificadas em outro arquivo (XML Schema). Este arquivo conterá a estrutura de dados compartilhada (centralizada) que permite a padronização e reutilização de tipos de dados entre os serviços do domínio de negócio.

<?xml version="1.0" encoding="UTF-8"?>
<schema
targetNamespace="http://data.rafaelleonhardt.com.br/soacontractfirstcomwcf45/megasena/v1"
elementFormDefault="qualified"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://data.rafaelleonhardt.com.br/soacontractfirstcomwcf45/megasena/v1">
<complexType name="Concurso">
<sequence>
<element name="Numero" type="int"></element>
<element name="Data" type="dateTime"></element>
<element name="Dezenas" type="tns:Dezenas"></element>
</sequence>
</complexType>
<complexType name="Dezenas">
<sequence>
<element name="Numero" type="int" minOccurs="6" maxOccurs="6"></element>
</sequence>
</complexType>
</schema>
view raw megasena.xsd hosted with ❤ by GitHub

O objetivo deste artigo não é entender em detalhes o WSDL e XSD, então se tiver dúvidas sobre como escrever um WSDL, consulte o tutorial do W3Schools.

Passo 2 – Gerar o código-fonte do contrato

O segundo passo é gerar código-fonte C# a partir do contrato (WSDL + XSD) que irá definir a Interface (C#) do serviço a ser utilizada no WCF assim como as classes de dados de Request e Response. O utilitário svcutil.exe é responsável por essa tarefa.

No prompt de comandos é executado o seguinte comando:

svcutil.exe /serviceContract /serializer:DataContractSerializer megasena.wsdl megasena.xsd

onde:
• /serviceContract ou apenas /sc – responsável por gerar o código-fonte do contrato.
• /serializer ou /ser – utiliza a classe DataContractSerializer do WCF para fazer a serialização e deserialização ao invés do XmlSerializer
• megasena.wsdl – é o arquivo de contract-first que define nosso contrato.
• megasena.xsd – é o arquivo de dados que podem ser reutilizados por mais de um serviço.

Gerando como resultado o arquivo “Megasena.cs”, o qual terá o código-fonte do contrato do serviço e pode ser adicionado ao Visual Studio para utilizar na implementação da lógica do serviço.

Observação: O utilitário svcutil.exe está disponível na pasta C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\ ou similar.

Passo 3 – Implementar o web service

O terceiro passo é utilizar o código-fonte gerado para implementar a lógica propriamente dita e expor o serviço através pelo WCF.

Inicialmente altera-se o web.config para que exponha o WSDL que foi escrito manualmente (Contract-First) ao invés de gerar automaticamente.
Para isso é utilizado o atributo “externalMetadataLocation” conforme abaixo.

<?xml version="1.0"?>
<configuration>
...
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata externalMetadataLocation="../megasena.wsdl" httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
...
</system.serviceModel>
...
</configuration>
view raw web.config hosted with ❤ by GitHub

Como resultado, o aplicativo consumidor ao invés de obter o WSDL a partir de “meuserviço.svc?wsdl” (automático) irá acessar o “megasena.wsdl” diretamente como veremos no Passo 4. Uma questão importante aqui é que o Address definido no WSDL precisa especificar o endereço de endpoint onde será publicado o serviço, ou seja, se este mesmo serviço for exposto em diferentes ambientes (desenvolvimento, homologação e produção) é necessário que cada um desses ambientes tenha o seu WSDL no qual especifica a URI de host do serviço.

Essa alteração no Address do WSDL conforme o ambiente é importante pois é comum utilizar ferramentas que criam o Proxy de acesso ao serviço, e neste caso, as ferramentas utilizam o Address especificado no WSDL para configurar o endereço de chamada ao serviço. Se isto não for feito, o aplicativo cliente poderá acusar erro por não conseguir se comunicar com o Address especificado no WSDL ou ainda se comunicar com um ambiente que não deveria.

Além da alteração no web.config, é necessário implementar o serviço propriamente dito. Para isso, é adicionado dois novos itens:

Megasena.svc – responsável por ser a porta de entrada do serviço

<%@ ServiceHost Service="RafaelLeonhardt.Artigos.SOAContractFirstComWCF45.MegasenaImplementation"
CodeBehind="Megasena.svc.cs" Language="C#" Debug="true" %>
view raw megasena.svc hosted with ❤ by GitHub

MegasenaSvc.cs – responsável pela lógica de implementação do serviço.
Esta classe de implementação é quem utiliza o código-fonte gerado pelo contrato para expor o serviço.

using System;
using data.rafaelleonhardt.com.br.soacontractfirstcomwcf45.megasena.v1;
namespace RafaelLeonhardt.Artigos.SOAContractFirstComWCF45
{
public class MegasenaImplementation : MegasenaService
{
public ConcursoResponse ObterConcurso(ConcursoRequest request)
{
return new ConcursoResponse
{
Concurso = new Concurso
{
Numero = request.Numero,
Data = DateTime.Today,
Dezenas = new Dezenas
{
10, 20, 30, 40, 50, 60
}
}
};
}
}
}
view raw MegasenaSvc.cs hosted with ❤ by GitHub

Observação: outra abordagem de gerar o código-fonte é através do WCF Client Tool, mas não tratarei dela neste artigo. Para saber mais consulte http://sammanipalansuriya.blogspot.com.br/2013/03/contract-first-development-in-wcf-45.html.

Passo 4 – Testar

Uma forma fácil de testar o serviço é acessá-lo pelo browser.

Ao acessar a url do serviço WCF é possível consultar o WSDL exposto e reparar que é o arquivo escrito manualmente (/megasena.wsdl) ao invés do tradicional megasena.svc?wsdl.

 

Além disso é possível acessar o arquivo de Schema (XSD).

Outra forma de testar o serviço é utilizar o WCF Test Client (C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\WcfTestClient.exe).

Neste aplicativo é interessante notar o binding e endpoint definidos no WSDL.

Ao chamar a operação ObterResultado é possível avaliar as mensagens trocadas com os namespaces e formatos de dados definidos na abordagem Contract-First.

Pronto! Temos um serviço simples publicado com WCF e desenvolvido na abordagem Contract-First.

Se tiver alguma dúvida ou algo a compartilhar não deixe de comentar.

Os artefatos gerados neste artigo estão disponíveis no GitHub.

Até mais!

Rafael Leonhardt
@MumHaBR

Referências

Livro – SOA Principles of Service Design de Thomas Erl
• Contract-first Too – http://msdn.microsoft.com/en-us/library/hh674270.aspx
• ServiceModel Metadata Utility Tool (Svcutil.exe) – http://msdn.microsoft.com/en-us/library/aa347733(v=vs.110).aspx
• How To Create Service Contracts From Wsdl – http://www.bloggedbychris.com/2012/08/08/net-4-5-contract-development-create-service-contracts-wsdl/
• Contract First Development in WCF 4.5 – http://sammanipalansuriya.blogspot.com.br/2013/03/contract-first-development-in-wcf-45.html
• Why Contract-First: http://docs.spring.io/spring-ws/sites/2.0/reference/html/why-contract-first.html