Nel framework 3.5 abbiamo il supporto diretto all'implementazione di servizi POX all'interno di WCF. In questo post intendo dunque mostrare come generare e trasmettere messaggi "plain old XML" (ovvero costituiti esclusivamente da un payload XML) utilizzando il programming model messo a disposizione da WCF.
Domanda: quando si ha bisogno di inviare/ricevere messaggi POX?
Risposta: quando i client non supportano nativamente protocolli basati su SOAP oppure quando è richiesto un "leggero" scambio di dati via HTTP senza alcun requisito relativo a standard come WS-* (es. invio di un semplice feed RSS). In aggiunta, un'infrastruttura POX/HTTP costituisce la base naturale per l'implementazione di applicazioni Web REST-ful (es. il client invia una richiesta HTTP del tipo "GET http://.../customers/39" e il service risponde con un semplice documento POX contenente le informazioni di un customer).
Vediamo dunque come realizzare un semplice scenario client-server WCF per lo scambio di messaggi POX su HTTP:
Service
Partiamo da un contratto ICustomerService di esempio, che prevede un solo metodo ProcessMessage:
[ServiceContract]
public interface ICustomerService
{
[OperationContract(Action = "*", ReplyAction = "*", IsOneWay=false, ProtectionLevel=System.Net.Security.ProtectionLevel.None)]
Message ProcessMessage(Message request);
}
Consideriamo immediatamente due aspetti:
- Nel contesto di default di WCF, gli attributi Action e ReplyAction servono per impostare rispettivamente l'azione WS-Addressing del messaggio di richiesta e l'azione SOAP relativa al messaggio di risposta. Infatti, per default WCF utilizza le azioni specificate nell'header SOAP per elaborare correttamente i messaggi scambiati tra client e service. Nel nostro caso, la "wildcard action" (*) bypassa questo comportamento e permette quindi il dispatch di un qualunque messaggio ricevuto dell'endpoint del servizio verso il nostro metodo ProcessMessage.
- Sia l'argomento che il valore restituito dal metodo ProcessMessage sono degli oggetti di tipo System.ServiceModel.Channels.Message, una nuova astrazione che ci permette di creare e manipolare messaggi "raw", nonostante essa rappresenti nativamente una busta SOAP a tutti gli effetti.
Tutto diventa più chiaro se osserviamo l'implementazione del servizio: la classe CustomerService elabora una richiesta POX in ingresso e restituisce un Message contenente dati XML relativi ad un Customer.
public class CustomerService : ICustomerService
{
public Message ProcessMessage(Message request)
{
XmlDictionaryReader reader = request.GetReaderAtBodyContents();
string customerID = reader.ReadElementContentAsString();
Customer customer = GetCustomerById(customerID);
Message response = Message.CreateMessage(MessageVersion.None, "*", new CustomerInfoBodyWriter(true, customer));
return response;
}
}
Per creare un messaggio POX specifico per le nostre esigenze utilizzando la classe Message, abbiamo semplicemente bisogno di invocarne il metodo statico CreateMessage, il quale possiede diversi overload a supporto del trasferimento in modalità sia push che pull. In questo caso è stato usato l'overload "push-mode" che si avvale di un BodyWriter custom ottenuto ereditando la classe base astratta, in modo da avere il pieno controllo sulla creazione del messaggio POX da inviare al client tramite l'override del metodo OnWriteBodyContents.
public class CustomerInfoBodyWriter : BodyWriter
{
private Customer _customer;
public CustomerInfoBodyWriter(bool isBuffered, Customer customer) : base(isBuffered)
{
_customer = customer;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("CustomerInfo");
writer.WriteAttributeString("id", _customer.ID);
writer.WriteAttributeString("name", _customer.Name);
writer.WriteAttributeString("email", _customer.Email);
writer.WriteEndElement();
writer.Flush();
}
}
Vediamo infine la configurazione dell'endpoint, che si interfaccia con la rete tramite un customBinding che prevede la generazione di stream su HTTP facendo uso di un encoder che permette la ricezione e l'invio di messaggi POX privi di un qualunque incapsulamento SOAP (<textMessageEncoding messageVersion="None" />).
<configuration>
<system.serviceModel>
<bindings>
<customBinding>
<binding name="poxBinding">
<textMessageEncoding messageVersion="None" />
<httpTransport transferMode="StreamedResponse" maxBufferSize="1024" />
</binding>
</customBinding>
</bindings>
<services>
<service name="SamplePOXService.CustomerService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:4971/" />
</baseAddresses>
</host>
<endpoint address="customers" binding="customBinding" bindingConfiguration="poxBinding"
contract="ServiceClassLibrary.ICustomerService" />
</service>
</services>
</system.serviceModel>
</configuration>
Client
Il client POX condivide ovviamente la stessa configurazione del servizio, in maniera tale che la richiesta generata sia anch'essa POX su HTTP. Ecco quindi un semplice esempio di client WCF che genera un Message POX di richiesta contenente un ID di un Customer e stampa quindi a video l'XML risultante dall'invocazione del servizio.
ChannelFactory<ICustomerService> channelFactory = new ChannelFactory<ICustomerService>("CustomerService_ClientEndPoint");
ICustomerService customer_service = channelFactory.CreateChannel();
string customerID = "1";
Message request = Message.CreateMessage(MessageVersion.None, "*", customerID);
Message response = customer_service.ProcessMessage(request);
XmlDictionaryReader reader = response.GetReaderAtBodyContents();
Console.WriteLine("{0}", reader.ReadOuterXml());
Console.ReadLine();
Analizzando il traffico generato per l'esempio proposto, si rileva che i messaggi scambiati sono effettivamente di dimensioni minime. Degna di nota è inoltre l'introduzione dell' xmlns nella generazione di default di un Message POX ottenuto tramite l'overload di CreateMessage che prevede il BodyWriter di default:
Request: <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">1</string>
Response: <CustomerInfo id="1" name="Dario Santarelli" email="dario.santarelli@xxx.yy" />
Come già anticipato all'inizio di questo post, l'utilizzo di un' infrastruttura POX/HTTP è consigliabile per:
- soluzioni in cui i client non possiedono nativamente il supporto allo standard SOAP o comunque a protocolli non-HTTP
- scenari in cui lo scambio dati deve essere il più leggero possibile ed in cui occorre aumentare sensibilmente le prestazioni del ciclo "request-response" (vedi AJAX).
- implementazioni REST-ful
Chiaramente un esempio come quello mostrato non contempla affatto l'aspetto "security": HTTPS a parte, se si intende utilizzare POX/HTTP senza alcun meccanismo di autenticazione ed encryption dei dati, è comunque obbligatorio NON trasmettere messaggi contenenti dati sensibili.
Per maggiori informazioni, consiglio la lettura di questo articolo MSDN: REST and POX
Technorati tags: POX, REST, WCF