Stazione di servizio
Messaggi WCF: elementi di base
Aaron Skonnard
Download codice disponibile in:
ServiceStation2007_04.exe
(161 KB)
Browse the Code Online

Sommario
Quando si inizia a conoscere Windows® Communication Foundation, viene individuato un framework di messaggistica sofisticato basato su XML che offre infinite possibilità di collegamento dei sistemi tramite diversi protocolli e formati. Nella rubrica di questo mese, evidenzio alcune delle funzionalità principali di messaggistica caratterizzate da tale flessibilità.
Questa rubrica presuppone una conoscenza di base del modello di programmazione Windows Communication Foundation. Se questo argomento non è mai stato trattato in precedenza, prima di procedere è necessario leggere
l'articolo di
MSDN®Magazine pubblicato nel febbraio del 2006.
Uno degli scopi primari dell'architettura di messaggistica Windows Communication Foundation è fornire un modello di programmazione unificato e consentire, allo stesso tempo, flessibilità nella modalità di visualizzazione dei dati e di trasmissione dei messaggi. Tale obiettivo viene raggiunto basandosi sull'XML come modello di dati oltre a SOAP e WS-Addressing come framework di messaggistica. Tuttavia, il fatto che Windows Communication Foundation si basi su questi modelli non implica che è necessario l'utilizzo di XML 1.0, SOAP o WS-Addressing durante la trasmissione dei messaggi. È possibile notare che Windows Communication Foundation fornisce grande flessibilità.
Rappresentazioni XML
Sin dalla nascita dell'XML, il settore informatico ha fatto affidamento su una specifica poco nota che fornisce una definizione standard per i dati trovati in un documento XML. Questa specifica, indicata con la definizione Information Set (InfoSet) XML, definisce gli elementi e gli attributi in termini di informazioni che contengono, in una maniera completamente indipendente dalla rappresentazione in byte. Per i dettagli, vedere
www.w3.org/TR/xml-infoset.
La specifica InfoSet ha consentito ad altre specifiche XML e API di offrire una panoramica coerente dei dati trovati in un documento XML, anche se potrebbero rappresentare tali dati in maniera completamente diversa. In definitiva, InfoSet fornisce un punto di incontro comune per le applicazioni che lavorano con i dati XML, come illustrato nella Figura 1. In sostanza, è compito di un processore XML eseguire la conversione tra la rappresentazione in byte e l'esperienza del modello di programmazione.
Figura 1 Il ruolo di InfoSet XML (Fare clic sull'immagine per ingrandirla)
Windows Communication Foundation introduce alcuni miglioramenti fondamentali nello spazio dei nomi System.Xml che rende possibile utilizzare rappresentazioni in byte alternative piuttosto che espressioni letterali XML 1.0 durante la lettura e la scrittura di documenti XML. Le principali classi di interesse, System.Xml.XmlDictionaryReader e System.Xml.XmlDictionaryWriter, si trovano nel nuovo assembly System.Runtime.Serialization fornito con Microsoft® .NET Framework 3.0.
Entrambe le classi XmlDictionaryReader e XmlDictionaryWriter forniscono metodi factory statici per creare programmi di lettura e scrittura che utilizzano rappresentazioni di testo, binarie e MTOM (Message Transmission Optimization Mechanism). Ad esempio, XmlDictionaryReader fornisce i metodi CreateTextReader, CreateBinaryReader e CreateMtomReader mentre XmlDictionaryWriter fornisce i metodi CreateTextWriter, CreateBinaryWriter e CreateMtomWriter corrispondenti.
Ecco alcuni esempi. Considerare questa rappresentazione letterale XML 1.0 di un cliente:
<!-- customer.xml -->
<Customer xmlns="http://example.org/customer">
<Email>bob@contoso.com</Email>
<Name>Bob</Name>
</Customer>
Il seguente codice mostra come leggere il file customer.xml in un oggetto XmlDocument e come utilizzare XmlDictionaryWriter per mantenere lo stesso oggetto XmlDocument al di fuori di una rappresentazione binaria su disco:
// read from XML 1.0 text representation
XmlDocument doc = new XmlDocument();
doc.Load("customer.xml");
// write to binary representation
FileStream custBinStream = new FileStream(
"customer.bin", FileMode.Create);
using (XmlWriter xw = XmlDictionaryWriter.CreateBinaryWriter(
custBinStream))
{
doc.WriteContentTo(xw);
}
Dopo l'esecuzione di questo codice, viene generata una rappresentazione binaria salvata in customer.bin simile a quella illustrata nella Figura 2. Sebbene l'editor binario illustrato nella Figura 2 visualizzi una sequenza di caratteri, si tratta di fatto di un file binario e non somiglia ovviamente a un file XML 1.0. Tuttavia, rappresenta un documento XML (un InfoSet). Peraltro, il formato di questo file binario appartiene alla versione 3.0 di System.Xml e non è compatibile con altre versioni .NET o altri framework di servizi Web.
Figura 2 Rappresentazione binaria del documento XML del cliente (Fare clic sull'immagine per ingrandirla)
L'esempio di codice nella Figura 3 illustra come leggere la rappresentazione binaria in un oggetto XmlDocument e come mantenerla in una rappresentazione MTOM su disco. La rappresentazione MTOM appare molto diversa rispetto alle rappresentazioni in formato testo o binarie, come è possibile vedere nella Figura 4. Tuttavia, rappresenta lo stesso documento XML e può essere elaborato utilizzando utilizzare le stesse API di System.Xml. La rappresentazione MTOM differisce anche dalla rappresentazione binaria in cui MTOM è una raccomandazione W3C basata sull'InfoSet ed è ampiamente supportata tra i framework dei servizi Web. MTOM rende possibile ottimizzare la trasmissione di elementi binari all'interno di documenti XML facendo leva su frame MIME multiparte.

Figure 3 Leggere da un file binario e scrivere nelle rappresentazioni MTOM
// read from binary representation
XmlDocument doc = new XmlDocument();
FileStream custBinStream = new FileStream(
"customer.bin", FileMode.Open);
using (XmlReader xr = XmlDictionaryReader.CreateBinaryReader(
custBinStream, XmlDictionaryReaderQuotas.Max))
{
doc.Load(xr);
}
// write to MTOM representation
FileStream custMtomStream = new FileStream(
"customer.mtom", FileMode.Create);
using (XmlWriter xw = XmlDictionaryWriter.CreateMtomWriter(
custMtomStream, Encoding.UTF8, 1024, "text/xml"))
{
doc.WriteTo(xw);
}
Figura 4 Rappresentazione binaria del documento XML del cliente (Fare clic sull'immagine per ingrandirla)
È ora possibile tornare al punto di partenza leggendo la rappresentazione MTOM di nuovo in un XmlDocument e portandolo in una rappresentazione in formato testo su disco, come la seguente:
// read from MTOM representation
XmlDocument doc = new XmlDocument();
FileStream custMtomStream = new FileStream(
"customer.mtom", FileMode.Open);
using (XmlReader xr = XmlDictionaryReader.CreateMtomReader(
custMtomStream, Encoding.UTF8, XmlDictionaryReaderQuotas.Max))
{
doc.Load(xr);
}
// write to text (XML 1.0) representation
doc.Save("customer.xml");
Il file custom.xml che ne risulta deve sembrare identico alla rappresentazione del testo originale. Come si è visto, questi miglioramenti in System.Xml offrono grande flessibilità fornendo tre modi per rappresentare l'XML per permanenza e trasmissione. Naturalmente, in futuro è possibile aggiungere altre rappresentazioni. E sebbene sia stata l'architettura Windows Communication Foundation ad apportare questi miglioramenti alla tabella, è possibile utilizzarli in qualsiasi applicazione .NET Framework 3.0.
Windows Communication Foundation si basa su questi per consentire di scegliere quale rappresentazione XML utilizzare durante la trasmissione dei messaggi. Se si desidera maggiore interoperabilità, è necessario scegliere la rappresentazione di testo di XML 1.0. Se si desidera maggiore interoperabilità oltre a un supporto efficiente per payload binari, è necessario scegliere la rappresentazione MTOM. Inoltre, in scenari .NET, la rappresentazione binaria può fornire prestazioni migliori. Il punto fondamentale è che si ha effettivamente una scelta.
Classe Message
Un'altra funzionalità essenziale di qualsiasi framework di messaggistica è la capacità di estendere i payload del messaggio con intestazioni arbitrarie. Un'intestazione consiste semplicemente in informazioni aggiuntive che viaggiano col messaggio, utilizzate per implementare ulteriori funzionalità di elaborazione messaggi (come protezione, messaggistica affidabile e transazioni). In caso di messaggi XML, ciò vuol dire estendere un payload XML con intestazioni XML; sono rappresentati entrambi come elementi XML racchiusi in un elemento contenitore. Questa funzionalità è esattamente quella fornita da SOAP.
Il framework SOAP rende possibile la definizione di protocolli basati su XML che possono essere utilizzati su qualunque tipo di trasporto, ma senza fare affidamento su alcuna funzionalità specifica di trasporto. WS-Addressing è una specifica che estende il SOAP per fornire un meccanismo di trasporto neutrale per l'instradamento/indirizzamento dei messaggi SOAP. SOAP e WS-Addressing si basano entrambi su InfoSet e consentono (ma non richiedono) l'uso della sintassi XML 1.0 durante la trasmissione dei messaggi.
Windows Communication Foundation supporta l'uso di tutte le rappresentazioni XmlDictionaryReader/Writer trattate nella sezione precedente che consente alle applicazioni di comprendere diversi requisiti di portata e prestazioni. Windows Communication Foundation modella tutti i messaggi utilizzando la classe Message illustrata nella Figura 5. Come si può notare, la classe Message modella essenzialmente un corpo del messaggio e insiemi di intestazioni e proprietà di messaggi. I metodi disponibili sono indicati principalmente per la creazione di messaggi, per la lettura e scrittura del corpo dei messaggi e per la modifica degli insiemi di intestazioni e proprietà.

Figure 5 Classe System.ServiceModel.Channels.Message
public abstract class Message : IDisposable
{
// numerous overloads for creating messages
public static Message CreateMessage(...);
// creates a buffered copy of a message
public MessageBuffer CreateBufferedCopy(int maxBufferSize);
// closes a message
public void Close();
// returns an XmlDictionaryReader for reading the body
public XmlDictionaryReader GetReaderAtBodyContents();
// deserializes the body into a .NET object
public T GetBody<T>();
public T GetBody<T>(XmlObjectSerializer serializer);
// numerous methods/overloads for writing messages
public void WriteMessage(XmlDictionaryWriter writer);
public void WriteBody(XmlDictionaryWriter writer);
public override string ToString();
// properties
public abstract MessageHeaders Headers { get; }
public abstract MessageProperties Properties { get; }
public MessageState State { get; }
public abstract MessageVersion Version { get; }
public virtual bool IsEmpty { get; }
public virtual bool IsFault { get; }
...
}
Creare un oggetto Message richiamando uno dei vari overload CreateMessage statici ed eliminare un oggetto Message utilizzando IDisposable o chiamando esplicitamente Close. È possibile creare un nuovo messaggio da zero, operazione tipica quando si invia un messaggio. Oppure creare un messaggio nuovo da un flusso di messaggi, operazione tipica quando si ricevono messaggi.
Nel caso della creazione di un messaggio da zero, è necessario specificare l'azione, la versione del messaggio e il corpo da utilizzare all'interno del messaggio. L'azione identifica in modo univoco lo scopo o la semantica del messaggio. I servizi di Windows Communication Foundation fanno affidamento sull'azione per l'invio di messaggi in arrivo al metodo appropriato. La versione del messaggio identifica quali versioni di SOAP e WS-Addressing utilizzare durante la trasmissione, se sono disponibili. Esistono diverse opzioni da cui poter scegliere quando si specifica la versione del messaggio. Diamo un'occhiata a queste opzioni.
Versioni del messaggio
Come menzionato, la classe MessageVersion consente di specificare le versioni di SOAP e WS-Addressing che desideri utilizzare. È possibile creare un oggetto MessageVersion richiamando CreateVersion e fornendo un oggetto EnvelopeVersion (per identificare la versione SOAP) insieme a un oggetto AddressingVersion (per identificare la versione WS-Addressing). Il risultato è simile al seguente:
MessageVersion version = MessageVersion.CreateVersion(
EnvelopeVersion.Soap12, AddressingVersion.WSAddressing10);
Se si prende in considerazione la classe EnvelopeVersion, è possibile notare che attualmente Windows Communication Foundation supporta tre opzioni per SOAP: None, Soap11 e Soap12, come illustrato si seguito:
public sealed class EnvelopeVersion
{
public static EnvelopeVersion None { get; }
public static EnvelopeVersion Soap11 { get; }
public static EnvelopeVersion Soap12 { get; }
...
}
Allo stesso modo, se si esamina la classe AddressingVersion, è possibile notare che attualmente Windows Communication Foundation supporta tre opzioni per WS-Addressing: None, WSAddressing10, e WSAddressingAugust2004, come illustrato di seguito:
public sealed class AddressingVersion
{
public static AddressingVersion None { get; }
public static AddressingVersion WSAddressing10 { get; }
public static AddressingVersion WSAddressingAugust2004 { get;}
...
}
La classe EnvelopeVersion.None indica che non si desidera utilizzare SOAP durante la trasmissione, ma che si richiede anche l'utilizzo della classe AddressingVersion.None. Si tratta di un'impostazione comune per quando si desidera utilizzare Windows Communication Foundation in vecchi scenari di messaggistica XML. Soap11 rappresenta la specifica SOAP 1.1, attualmente largamente utilizzata, mentre Soap12 rappresenta la raccomandazione W3C per SOAP 1.2 (per i collegamenti a entrambe le specifiche vedere
www.w3.org/TR/soap).
WSAddressingAugust2004 rappresenta la versione originale WS-Addressing W3C del mese di agosto del 2004 (visitare la pagina
www.w3.org/Submission/ws-addressing), attualmente ampiamente supportata. WSAddressing10 rappresenta la versione finale WS-Addressing 1.0 W3C Recommendation (visitare la pagina
www.w3.org/2002/ws/addr).
Pertanto, in base alle versioni SOAP e WS-Addressing supportate in rete, è possibile individuare e scegliere la versione giusta che consente di facilitare l'interoperabilità. E per facilitare le cose, la classe MessageVersion fornisce diverse proprietà pubbliche che restituiscono gli oggetti MessageVersion memorizzati nella cache che rappresentano le combinazioni più comuni di entrambe le specifiche (vedere la Figura 6). Pertanto, invece di chiamare esplicitamente CreateVersion, è possibile specificare semplicemente MessageVersion.Soap12WSAddressing10, che è simile a MessageVersion.Default.

Figure 6 Oggetti MessageVersion memorizzati nella cache
public sealed class MessageVersion
{
public static MessageVersion Default { get; }
public static MessageVersion None { get; }
public static MessageVersion Soap11 { get; }
public static MessageVersion Soap11WSAddressing10 { get; }
public static MessageVersion Soap11WSAddressingAugust2004
{ get; }
public static MessageVersion Soap12 { get; }
public static MessageVersion Soap12WSAddressing10 { get; }
public static MessageVersion Soap12WSAddressingAugust2004
{ get; }
... //
}
Lettura e scrittura di messaggi
Quando viene creato un messaggio da zero, sono disponibili diversi overload che consentono di specificare il corpo del messaggio. È possibile fornire il corpo come un oggetto XmlDictionaryReader, XmlReader o serializzabile. Inoltre, è possibile fornire un oggetto MessageFault per il corpo per creare un messaggio di errore. Una volta creato il messaggio, è possibile accedere al corpo chiamando GetReaderAtBodyContents, che restituisce XmlReader o GetBody<T> per deserializzare il corpo in un oggetto .NET.
Se si desidera scrivere un messaggio, è possibile scrivere l'intero messaggio o solo il corpo chiamando rispettivamente i metodi WriteMessage o WriteBody. Entrambi dispongono di overload che consentono di fornire un oggetto XmlDictionaryWriter o XmlWriter. Nel seguente esempio viene illustrato come creare un nuovo messaggio scritto, successivamente, in un file chiamato message.xml:
// load body from customer.xml file
XmlDocument doc = new XmlDocument();
doc.Load("customer.xml");
Message m = Message.CreateMessage(
MessageVersion.Soap11WSAddressingAugust2004,
"urn:add-customer", new XmlNodeReader(doc));
FileStream fs = new FileStream("message.xml", FileMode.Create);
using (XmlWriter xw = XmlDictionaryWriter.CreateTextWriter(fs))
{
m.WriteMessage(xw);
}
In questo caso, viene indicato "urn:add-customer" per l'azione e Soap11WSAddressing2004 per la versione del messaggio. Il corpo del messaggio viene fornito tramite un XmlReader. Viene poi chiamato WriteMessage per scrivere il messaggio in un oggetto XmlDictionaryWriter basato sul testo. Il file message.xml che ne risulta è simile a quanto segue:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">urn:add-customer</a:Action>
</s:Header>
<s:Body>
<Customer xmlns="http://example.org/customer">
<Email>bob@contoso.com</Email>
<Name>Bob</Name>
</Customer>
</s:Body>
</s:Envelope>
Tenere presente che gli spazi dei nomi XML indicano SOAP 1.1 e WS-Addressing dell'agosto del 2004. È importante evidenziare che è possibile scrivere facilmente il messaggio in un file binario o una rappresentazione MTOM utilizzando un oggetto XmlDictionaryWriter diverso.
A questo punto, se lo si desidera, è possibile modificare la versione in MessageVersion.Soap12WSAddressing10 e il file risultante sarebbe simile a quanto riportato di seguito:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">urn:add-customer</a:Action>
</s:Header>
<s:Body>
<Customer xmlns="http://example.org/customer">
<Email>bob@contoso.com</Email>
<Name>Bob</Name>
</Customer>
</s:Body>
</s:Envelope>
Tenere presente le versioni più recenti di SOAP e WS-Addressing.
Nel caso in cui fosse necessario modificare la versione in MessageVersion.None, il messaggio risultante non conterrebbe più una busta SOAP, ma semplicemente il corpo:
<Customer xmlns="http://example.org/customer">
<Email>bob@contoso.com</Email>
<Name>Bob</Name>
</Customer>
Per poter leggere il messaggio, è necessario utilizzare un overload CreateMessage diverso che consente di fornire un XmlDictionaryReader per leggere il flusso di messaggi. Inoltre, è necessario specificare la versione corretta del messaggio. Il codice riportato di seguito illustra un esempio che legge l'ultimo messaggio senza busta (la versione è MessageVersion.None):
FileStream fs = new FileStream("message.xml", FileMode.Open);
using (XmlDictionaryReader reader =
XmlDictionaryReader.CreateTextReader(
fs, XmlDictionaryReaderQuotas.Max))
{
Message m = Message.CreateMessage(reader, 1024,
MessageVersion.None);
XmlDocument doc = new XmlDocument();
doc.Load(m.GetReaderAtBodyContents());
Console.WriteLine(doc.InnerXml);
}
Come si può notare, la classe Message fornisce grande flessibilità nel leggere e scrivere i messaggi utilizzando versioni diverse di SOAP e WS-Addressing. Inoltre, poiché si basa su XmlDictionaryReader per le operazioni di lettura e scrittura implica che è possibile utilizzare tutte le rappresentazioni XML supportate durante la trasmissione dei messaggi.
Corpi dei messaggi tipizzati
Finora, sono stati illustrati degli esempi di lavoro direttamente con l'XML. Se si desidera deserializzare l'XML in oggetti .NET, è possibile utilizzare uno dei serializzatori supportati, come DataContractSerializer, NetDataContractSerializer o XmlSerializer. La classe seguente è in grado di rappresentare l'XML trovato nel file customer.xml quando utilizzato con DataContractSerializer:
[DataContract(Namespace="http://example.org/customer")]
public class Customer
{
public Customer() { }
public Customer(string name, string email)
{
this.Name = name;
this.Email = email;
}
[DataMember]
public string Name;
[DataMember]
public string Email;
}
A questo punto è possibile creare dei messaggi fornendo oggetti Customer per il corpo del messaggio, come segue:
Customer cust = new Customer("Bob", "bob@contoso.com");
Message msg = Message.CreateMessage(
MessageVersion.Soap11WSAddressingAugust2004,
"urn:add-customer", cust);
Nonostante si utilizzi un oggetto per rappresentare il corpo, il messaggio che ne risulta sarà identico agli esempi precedenti.
A questo punto, nel leggere il corpo è possibile utilizzare GetBody<Customer> per produrre un nuovo oggetto Customer, come illustrato di seguito:
Customer c = msg.GetBody<Customer>();
Console.WriteLine("Name: {0}, Email: {1}", c.Name, c.Email);
Queste versioni di CreateMessage e GetBody<T> utilizzano DataContractSerializer dietro le quinte. Esiste un'altra versione di ciascun metodo che consente di specificare esplicitamente il serializzatore che si desidera utilizzare.
Per ulteriori dettagli, leggere l'articolo pubblicato nell'agosto del 2006, dal titolo "Serializzazione in Windows Communication Foundation" (disponibile all'indirizzo
msdn.microsoft.com/msdnmag/issues/06/08/ServiceStation).
Durata messaggio
La classe Message è stata progettata attentamente per supportare il flusso. Di conseguenza, è possibile elaborare il corpo del messaggio una sola volta durante il ciclo di vita di un oggetto Message. Per sicurezza, la classe Message fornisce una proprietà State che descrive lo stato attuale dell'oggetto. MessageState definisce cinque possibili stati di messaggio:
public enum MessageState
{
Created,
Read,
Written,
Copied,
Closed
}
Un oggetto Message viene avviato nello stato Created, ossia il solo stato valido per elaborare il corpo. Esistono diversi metodi per elaborare il corpo: è possibile leggerlo, scriverlo o copiarlo. La chiamata a GetReaderAtBodyContents o GetBody<T> modifica lo stato in Read. La chiamata a WriteMessage o WriteBody modifica lo stato in Written. Infine, la chiamata a CreateBufferedCopy modifica lo stato in Copied. Ad esempio, considerare l'esempio seguente che fa passare un messaggio attraverso diversi stati:
Customer cust = new Customer("Bob", "bob@contoso.com");
Message m = Message.CreateMessage(
MessageVersion.Soap11WSAddressingAugust2004,
"urn:add-customer", cust);
Console.WriteLine("State: {0}", m.State);
Customer c = m.GetBody<Customer>();
// cannot access body from here on
Console.WriteLine("State: {0}", m.State);
m.Close();
Console.WriteLine("State: {0}", m.State);
Quando si esegue il codice, questo testo viene stampato nella console:
State: Created
State: Read
State: Closed
Una volta che l'oggetto Message non si trova più nello stato Created, qualunque metodo che richieda l'accesso al corpo genererà un'eccezione. Ad esempio, la chiamata a GetBody<T> dopo la prima chiamata genera un'eccezione. Nei casi in cui è necessario elaborare il corpo più volte, è possibile creare una copia memorizzata nel buffer (chiamando CreateBufferedCopy) o caricare il corpo in un XmlDocument o deserializzarlo in un oggetto .NET per un uso in memoria.
Inutile dire che la proprietà State rende possibile determinare in fase di esecuzione se il corpo di un determinato Message è stato già utilizzato (quando non è più nello stato Created) e come è stato utilizzato (se è stato letto, scritto o copiato).
Intestazioni e proprietà del messaggio
La classe Message fornisce una proprietà Headers che consente di modellare l'insieme di intestazioni associate al corpo. L'esempio seguente illustra come aggiungere un'intestazione personalizzata chiamata "ContextId" a un messaggio:
Customer cust = new Customer("Bob", "bob@contoso.com");
Message m = Message.CreateMessage(
MessageVersion.Default, "urn:add-customer", cust);
m.Headers.Add(
MessageHeader.CreateHeader(
"ContextId", "http://example.org/customHeaders",
Guid.NewGuid()));
Quando questo messaggio viene scritto, è simile al seguente:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">urn:add-customer</a:Action>
<ContextId xmlns="http://example.org/customHeaders"
>a200f76d-5b83-4496-a035-4f9e70a07959</ContextId>
</s:Header>
...
</s:Envelope>
È generalmente compito degli intermediari elaborare le intestazioni trovate in un messaggio. Tali intermediari devono memorizzare spesso i risultati dell'elaborazione in un punto qualsiasi per uso futuro nella pipeline di elaborazione. A tale scopo, la classe Message fornisce anche un insieme Properties per memorizzare coppie arbitrarie di nome/oggetto.
A differenza delle intestazioni, che vengono sempre trasmesse nel messaggio, le proprietà del messaggio vengono generalmente utilizzate soltanto durante l'elaborazione locale; tali proprietà possono non avere alcun impatto su ciò che avviene in rete, sebbene a volte succeda. Ad esempio, quando si utilizza HTTP, Windows Communication Foundation memorizza i dettagli della richiesta HTTP nell'insieme Properties dell'oggetto Message in entrata. I dettagli vengono memorizzati in un oggetto di tipo HttpRequestMessageProperty, a cui è possibile accedere utilizzando httpRequest come nome della proprietà. È anche possibile determinare i dettagli della risposta HTTP popolando una proprietà chiamata httpResponse con un oggetto di tipo HttpReponseMessageProperty.
Generalmente, i componenti all'interno del livello del canale di Windows Communication Foundation utilizzano molto le intestazioni e le proprietà, soprattutto i canali di protocollo incorporati che implementano le varie specifiche WS-*.
Mappatura dei messaggi sui metodi
Prima di poter iniziare a inviare o ricevere messaggi con Windows Communication Foundation, è necessario definire un contratto di servizio che mappi i messaggi in entrata sui metodi. Ciò avviene associando i valori delle azioni alle firme del metodo. Ad esempio, la seguente interfaccia definisce il contratto di servizio più universale possibile per un'operazione unidirezionale:
[ServiceContract]
public interface IUniversalOneWay
{
[OperationContract(Action="*")]
void ProcessMessage(Message msg);
}
Questa definizione associa ProcessMessage a tutti i messaggi in entrata a prescindere dai relativi valori di azione (grazie alla clausola Action= "*"). È anche possibile definire un'operazione di richiesta-risposta generica utilizzando la stessa tecnica:
[ServiceContract]
public interface IUniversalRequestReply
{
[OperationContract(Action="*", ReplyAction="*")]
Message ProcessMessage(Message msg);
}
Inoltre, è possibile associare dei metodi a valori di azione specifici impostando la proprietà Action su un valore specifico quale urn:add-customer, come illustrato di seguito:
[ServiceContract]
public interface ICustomer
{
[OperationContract(Action="urn:add-customer")]
void AddCustomer(Message msg);
}
Quando Windows Communication Foundation riceve un messaggio in entrata, esamina l'azione per determinare quale metodo inviare, quindi esegue qualsiasi serializzazione necessaria. Quando si utilizza Message per i tipi di richiesta/risposta, Windows Communication Foundation non esegue alcuna serializzazione, è l'utente che decide come elaborare il messaggio. Tuttavia, quando si utilizzano firme tipizzate, Windows Communication Foundation automatizza il processo di lettura e scrittura dei corpi dei messaggio utilizzando tecniche di serializzazione. Ad esempio, prendere in considerazione questo contratto di servizio:
[ServiceContract]
public interface ICustomer
{
[OperationContract(Action="urn:add-customer")]
void AddCustomer(Customer cust);
}
In questo caso, Windows Communication Foundation si occupa di deserializzare il corpo del messaggio in entrata in un oggetto Customer prima di chiamare AddCustomer.
Esiste un altro metodo rapido per la serializzazione, nota con la definizione contratti di messaggio, per aggiungere automaticamente le intestazioni a un oggetto Message. I contratti del messaggio consentono di annotare una classe, specificando quali campi mappano le intestazioni rispetto al corpo:
[MessageContract(IsWrapped=false)]
public class AddCustomerRequest
{
[MessageHeader(Name="ContextId",
Namespace="http://example.org/customHeaders"]
public Guid ContextId;
[MessageBodyMember]
public Customer customer;
}
Con questa classe di contratto di messaggio attiva, è possibile definire un'operazione che la utilizza come tipo di richiesta, come illustrato di seguito:
[ServiceContract]
public interface ICustomer
{
[OperationContract(Action = "urn:add-customer")]
void AddCustomer(AddCustomerRequest request);
}
Ciò indica a Windows Communication Foundation di mappare automaticamente il campo ContextId nell'oggetto all'intestazione ContextId nel messaggio SOAP, evitando di dover utilizzare manualmente l'insieme Headers.
Endpoint e associazioni
Per poter associare un servizio Windows Communication Foundation, è necessario fornire diverse informazioni. Per prima cosa, indicare il tipo di trasporto e l'indirizzo da utilizzare. Secondariamente, è necessario specificare quale rappresentazione XML e versione di messaggio è prevista. In terzo luogo, è necessario configurare i protocolli WS-* da associare. Infine, fornire il mapping tra i messaggi in entrata e i metodi di servizio. In Windows Communication Foundation indicare tutti questi dettagli tramite gli endpoint.
La configurazione di un endpoint consiste semplicemente in una combinazione di un indirizzo, un'associazione e un contratto. Come già indicato in precedenza, il contratto definisce il mapping tra messaggi e metodi. L'associazione specifica i dettagli di messaggistica rimanenti. Specifica il trasporto, la rappresentazione XML e la versione del messaggio da utilizzare durante la trasmissione. Identifica, inoltre, quali protocolli WS-* devono essere inclusi durante la creazione dello stack del canale.
Un'associazione specifica un codificatore messaggi, che controlla effettivamente i dettagli sulla rappresentazione XML e la versione del messaggio. In fase di esecuzione, il codificatore del messaggio è il componente utilizzato dal canale di trasporto per leggere e scrivere i provenienti o destinati a un flusso. Windows Communication Foundation fornisce tre implementazioni del codificatore messaggi incorporate: TextMessageEncoder, BinaryMessageEncoder e MtomMessageEncoder. Tutte le classi si basano sulle nuove classi XmlDictionaryReader e XmlDictionaryWriter trattate in precedenza per supportare una determinata rappresentazione XML. Ogni classe arriva inoltre configurata per utilizzare una versione di messaggio specifica.
Com'è noto, Windows Communication Foundation viene fornito con associazioni incorporate per facilitare gli scenari comuni. Ciascuna associazione è configurata per utilizzare un particolare codificatore di messaggi. Ad esempio, sia BasicHttpBinding che WSHttpBinding sono configurati per utilizzare TextMessageEncoder. Tuttavia, il primo utilizza MessageVersion.Soap11 mentre il secondo utilizza MessageVersion.Soap12WSAddressing10 per la versione del messaggio. NetTcpBinding, NetNamedPipeBinding e NetMsmqBinding vengono tutti configurati per utilizzare BinaryMessageEncoder e MessageVersion.Soap12WSAddressing10.
È possibile scegliere di utilizzare un codificatore di messaggi diverso in un'associazione particolare attraverso le relative proprietà o la configurazione di un'associazione. Ad esempio, quanto riportato di seguito illustra come trasformare il codificatore di messaggi BasicHttpBinding in MtomMessageEncoder:
BasicHttpBinding basic = new BasicHttpBinding();
basic.MessageEncoding = WSMessageEncoding.Mtom;
... // use binding
Se si necessita di maggior controllo sulla configurazione dell'associazione, è possibile definire un'associazione personalizzata utilizzando la classe CustomBinding:
MtomMessageEncodingBindingElement mtom =
new MtomMessageEncodingBindingElement(
MessageVersion.Soap12, Encoding.UTF8);
CustomBinding binding = new CustomBinding();
binding.Elements.Add(mtom);
binding.Elements.Add(new HttpTransportBindingElement());
... // use binding
Questa tecnica consente di configurare la versione corretta del messaggio e i dettagli di codifica del testo utilizzati dal codificatore del messaggio. Oltre a questa flessibilità, l'architettura Windows Communication Foundation consente di scrivere e utilizzare il codificatore di messaggi personalizzati; SDK viene fornito con un esempio che illustra le modalità con cui eseguire queste operazioni. In generale, Windows Communication Foundation facilita la configurazione degli endpoint per utilizzare varie rappresentazioni XML e versioni di messaggio senza avere alcun impatto sul codice del servizio.
Riepilogo
Esaminare l'ultimo esempio che raggruppa la maggior parte di questi concetti di messaggistica. Nella Figura 7 viene illustrato come implementare, ospitare e configurare un servizio generico che utilizza il contratto IUniversalOneWay con un'associazione e un codificatore personalizzati. Nella Figura 8 viene illustrato come implementare un client in grado di inviare messaggi al servizio ospitato. L'implementazione del servizio utilizza System.Xml per elaborare il messaggio in entrata mentre il client produce un messaggio utilizzando un oggetto Customer.

Figure 8 Client WCF generico
class Program
{
static void Main(string[] args)
{
Console.ReadLine();
// create and configure an MTOM encoder
MtomMessageEncodingBindingElement mtom =
new MtomMessageEncodingBindingElement(
MessageVersion.Soap12, Encoding.UTF8);
// create a CustomBinding using MTOM encoder
CustomBinding binding = new CustomBinding();
binding.Elements.Add(mtom);
binding.Elements.Add(new HttpTransportBindingElement());
// create a ChannelFactory using the custom binding
ChannelFactory<IUniversalOneWay> cf =
new ChannelFactory<IUniversalOneWay>(binding);
IUniversalOneWay channel = cf.CreateChannel(
new EndpointAddress(
"http://localhost:8080/customerservice"));
// create a Message and send through channel
Customer cust = new Customer("Bob", "bob@contoso.com");
Message msg = Message.CreateMessage(
MessageVersion.Soap12, "urn:add-customer", cust);
channel.ProcessMessage(msg);
}
}

Figure 7 Servizio WCF generico
public class CustomerService : IUniversalOneWay
{
#region IUniversalOneWay Members
public void ProcessMessage(Message msg)
{
Console.WriteLine("Message received...");
XmlDocument doc = new XmlDocument();
doc.Load(msg.GetReaderAtBodyContents());
Console.WriteLine("Customer: {0} ({1})",
doc.SelectSingleNode("/*/*[local-name()='Name']").InnerText,
doc.SelectSingleNode("/*/*[local-name()=
'Email']").InnerText);
}
#endregion
}
class Program
{
static void Main(string[] args)
{
using (ServiceHost host =
new ServiceHost(typeof(CustomerService),
new Uri("http://localhost:8080/customerservice")))
{
// create and configure an MTOM encoder
MtomMessageEncodingBindingElement mtom =
new MtomMessageEncodingBindingElement(
MessageVersion.Soap12, Encoding.UTF8);
// create a CustomBinding using MTOM encoder
CustomBinding binding = new CustomBinding();
binding.Elements.Add(mtom);
binding.Elements.Add(
new HttpTransportBindingElement());
// add an endpoint using the custom binding
host.AddServiceEndpoint(typeof(IUniversalOneWay),
binding, "");
host.Open();
Console.WriteLine("CustomerService is up and running...");
Console.ReadKey();
}
}
}
L'architettura di messaggistica di Windows Communication Foundation fornisce un modello di programmazione unificato che offre una notevole flessibilità al sistema di messaggistica. Ciò rende possibile implementare completamente i servizi XML semplici e vecchi che non utilizzano SOAP, WS-Addressing o qualsiasi altro protocollo WS-*. Rende anche possibile configurare le associazioni più sofisticate in modo da soddisfare le richieste di scenari d'integrazione specifici quando sono richiesti protocolli e versioni differenti.
Per inviare domande e commenti a Aaron, l'indirizzo è sstation@microsoft.com.
Aaron Skonnard è co-fondatore di Pluralsight, società che offre formazione per Microsoft .NET. È inoltre l'autore di Applied Web Services 2.0 di Pluralsight, Applied BizTalk Server 2006 e dei corsi introduttivi su Windows Communication Foundation. Aaron ha trascorso diversi anni a sviluppare corsi, a partecipare a conferenze come relatore e a insegnare agli sviluppatori professionisti. È possibile contattarlo all'indirizzo
pluralsight.com/aaron.