António Cruz

Partilha de Experiências com .NET

My Links

Blog Stats

Archives

Login

Saturday, February 10, 2007 #

Como fazer serviços REST com WCF

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 os parâmetros 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

[Cross-Posted de http://www.arquitecturadesoftware.org/blogs/antoniocruz]

 

posted @ 6:44 AM | Feedback (0)