Muitas vezes usar SOAP não é a melhor escolha de protocolo para acesso a um serviço. Isto é tanto verdade como dizer que usar REST nem sempre faz sentido, é claro. Tal como reza a máxima: "There's no silver bullets".
O REST popularizou-se ligado a serviços de informação não-sensitiva (que não usem dados pessoais, pagamentos, etc.), comunitários e sem grandes preocupações de automatização do consumo e descoberta. O SOAP, por seu lado, tem ganho notoriedade por oferecer a possibilidade de ser usado no contexto de soluções empresariais que recorrem a segurança, transacções, reliable messaging, queing e federação.
Isto quer dizer que provavelmente vamos continuar a desenvolver serviços aos quais acedemos usando HTTP GET e HTTP POST ainda por muito tempo. O REST é uma manifestação do uso de HTTP GET e POST e foi associado ao AJAX e JSON mas ao contrário do que possa parecer à primeira vista, não se tratam de tecnologias antagónicas, mas sim complementares.
As funcionalidades empresariais enunciadas acima correspondem às 4 grandes vertentes do WCF tal como ele se apresenta na actual versão 1.0. Para a versão 2.0 do WCF, está já previsto o suporte a publish-subscribing e a descoberta de serviços. Neste post, vou dar um exemplo de implementação de REST em WCF. O objectivo é duplo:
1) Que quem se inicia no WCF não desespere por achar que é impossível ou por não conseguir fazer os serviços HTTP GET e POST que já fazia com .ASMX (ou outra tecnologia);
2) Que possa ter a percepção do WCF como a API unificada para o desenvolvimento de serviços (tudo aquilo que se encontrava estava disperso no .ASMX, WSE, Remoting, COM/COM+ e Message Queuing).
Segue-se um exemplo de um contrato que possibilita usar REST:
[ServiceContract]
public interface ICalculator
{
[OperationContract(Action="*", ReplyAction="*")]
Message Calculate(Message message);
}
Em primeiro lugar, é digno de nota que tanto o parâmetro de input como o de output são do tipo Message. Traduzindo isto em linguagem menos "WCF-centric", temos: o método Calculate aceita qualquer tipo de input, o que faz deste parâmetro tão genérico como pode ser em WCF. O mesmo para o retorno: podemos devolver qualquer tipo de mensagem.
A razão disto é que o WCF encapsula automaticamente todo e qualquer pedido num objecto do tipo Message, por isso se dizemos que aceitamos Message e devolvemos Message é o mesmo que dizer "aceito qualquer coisa" e "devolvo qualquer coisa".
Em segundo lugar, note-se que tanto a propriedade Action como a ReplyAction do atributo OperationContract têm o valor "*". O WCF por default, usa SOAP. Isto quer dizer que espera receber um determinado valor num HTTP Header ou num elemento de WS-Addressing chamado "SOAPAction" e "Action", respectivamente. Se não queremos usar SOAP mas sim REST, então não queremos indicar qualquer Action/ReplyTo no momento da invocação, pelo que a equipa do WCF decidiu (e bem) que teria que haver uma forma de suportar mensagens não-SOAP. A forma que convencionaram é esta: o "*" faz com que o runtime não verifique o valor destes parâmetros e deixe a mensagem fluir para o interior da operação sem lançar a excepção: "The message with Action '' cannot be processed at the receiver".
Segue-se um serviço de exemplo de implementação do contrato analisado:
[ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)]
public class Calculator : ICalculator
{
public Message Calculate(Message message)
{
HttpRequestMessageProperty messageProperty =
message.Properties["httpRequest"] as HttpRequestMessageProperty;
if (messageProperty != null)
{
if (messageProperty.Method.Equals("GET", StringComparison.OrdinalIgnoreCase))
{
string[] segments = OperationContext.Current.IncomingMessageHeaders.To.Segments;
if (segments.Length > 2)
{
string operationName = segments[2].Replace("/", string.Empty).ToLower();
(...)
O serviço usa um ServiceBehavior que tem na propriedade AddressFilterMode o valor AddressFilterMode.Any. Isto quer dizer que não será analisado/validado o url usado para aceder ao endpoint do serviço. Por outras palavras, se podemos enviar qualquer payload *e* podemos usar qualquer tipo de sintaxe no url do serviço, temos o caminho aberto para usar REST com WCF.
Já no interior da operação, uso um HttpRequestMessageProperty para aceder ao pedido HTTP raw por via da propriedade httpRequest deste objecto. Assim, é fácil determinar qual o verbo HTTP que foi usado e agir em conformidade. Posso por exemplo processar os GET (select) de uma maneira e os POST (update) de outra, tal como convencionado em REST. E, é claro, processar qualquer outro verbo aceite pelo servidor da mesma forma (INSERT, APPEND, DEBUG, DELETE, etc.).
Finalmente, acedo a IncomingMessageHeaders para determinar o nome da operação. É interessante analisar os valores tanto de HttpRequestMessageProperty como de IncomingMessageHeaders, por isso sugiro que façam debug a esses objectos e naveguem pela informação que disponibilizam.
Este exemplo deixa claro como é fácil suportar REST em WCF. Considero boa prática suportar diversos protocolos de acesso à mesma funcionalidade, sempre que possível: por exemplo, suportar REST, JSON e SOAP, nesta altura, dá boas garantias de interoperabilidade com mashups de AJAX e deixa a porta aberta para o acesso via proxies de SOAP em C#, Java, PHP, Perl, etc.
António Cruz