Utilizzo della classe Message

La classe Message è essenziale per Windows Communication Foundation (WCF). Tutte le comunicazioni tra client e servizi implicano in ultima istanza l'invio e la ricezione di istanze di Message.

Di norma non si interagisce con la classe Message in modo diretto. Vengono invece utilizzati costrutti del modello di servizio WCF, quali contratti dati, contratti di messaggio e contratti di operazione, per descrivere i messaggi in ingresso e in uscita. Tuttavia, in alcuni scenari avanzati, è possibile eseguire la programmazione utilizzando direttamente la classe Message. Ad esempio, può essere necessario utilizzare la classe Message:

  • Quando è necessaria una modalità alternativa per creare il contenuto dei messaggi in uscita (ad esempio, creando un messaggio direttamente da un file sul disco), invece di serializzare oggetti .NET Framework.
  • Quando è necessaria una modalità alternativa per utilizzare il contenuto dei messaggi in arrivo (ad esempio, quando si desidera applicare una trasformazione XSLT al contenuto XML non elaborato), invece di eseguire la deserializzazione in oggetti .NET Framework.
  • Quando è necessario gestire i messaggi in modo generico, indipendentemente dal contenuto (ad esempio, per l'indirizzamento o l'inoltro di messaggi durante la creazione di un router, un servizio di bilanciamento del carico o un sistema di pubblicazione-sottoscrizione).

Prima di utilizzare la classe Message, acquisire dimestichezza con l'architettura di trasferimento dati WCF in Panoramica dell'architettura di trasferimento dei dati.

Una classe Message è un contenitore generico di dati, la cui progettazione segue però da vicino quella di un messaggio nel protocollo SOAP. Proprio come in SOAP, un messaggio ha un corpo e delle intestazioni. Il corpo del messaggio contiene i dati del payload effettivi, mentre le intestazioni contengono contenitori dei dati denominati aggiuntivi. Le regole per la lettura e la scrittura del corpo e delle intestazioni sono diverse, ad esempio, le intestazioni vengono sempre memorizzate nel buffer in memoria ed è possibile accedervi in qualsiasi ordine, tutte le volte che si desidera, il corpo può invece essere letto una sola volta ed essere trasmesso. In genere, quando si utilizza SOAP, il corpo del messaggio viene mappato al corpo SOAP e le intestazioni vengono mappate alle intestazioni SOAP.

Utilizzo della classe Message nelle operazioni

È possibile utilizzare la classe Message come parametro di input e/o valore restituito di un'operazione. Se la classe Message viene utilizzata in un punto qualsiasi di un'operazione, si applicano le restrizioni seguenti:

  • L'operazione non può avere qualsiasi parametro out o ref.
  • Non può essere presente più di un parametro input. Se il parametro è presente, deve essere di tipo Message o contratto di messaggio.
  • Il tipo restituito deve essere void, Message o un tipo di contratto di messaggio.

L'esempio di codice seguente contiene un contratto di operazione valido.

Creazione di messaggi di base

La classe Message fornisce metodi factory CreateMessage statici che è possibile utilizzare per creare messaggi di base.

Tutti gli overload CreateMessage accettano un parametro di versione di tipo MessageVersion, che indica le versioni SOAP e WS-Addressing da utilizzare per il messaggio. Se si desidera utilizzare le stesse versioni del protocollo del messaggio in ingresso, è possibile utilizzare la proprietà IncomingMessageVersion sull'istanza di OperationContext ottenuta dalla proprietà Current. La maggior parte degli overload CreateMessage hanno anche un parametro stringa che indica l'azione SOAP da utilizzare per il messaggio. È possibile impostare la versione su None per disabilitare la generazione di SOAP envelope. Il messaggio è costituito dal solo corpo.

Creazione di messaggi da oggetti

Tramite l'overload CreateMessage più elementare, che accetta solo una versione e un'azione, viene creato un messaggio con un corpo vuoto. Un altro overload accetta un parametro Object aggiuntivo, creando un messaggio il cui corpo è la rappresentazione serializzata dell'oggetto specificato. Utilizzare la classe DataContractSerializer con le impostazioni predefinite per la serializzazione. Se si desidera utilizzare un serializzatore diverso o si desidera che la classe DataContractSerializer venga configurata in modo diverso, utilizzare l'overload CreateMessage che accetta anche un parametro XmlObjectSerializer.

Per restituire un oggetto in un messaggio è, ad esempio, possibile utilizzare il codice seguente.

Creazione di messaggi da lettori XML

Esistono overload CreateMessage che accettano una classe XmlReader o XmlDictionaryReader per il corpo invece di un oggetto. In questo caso il corpo del messaggio contiene il codice XML prodotto dalla lettura del lettore XML passato. Ad esempio, nel codice seguente viene restituito un messaggio con il contenuto del corpo letto da un file XML.

Esistono, inoltre, overload CreateMessage che accettano una classe XmlReader o XmlDictionaryReader che rappresenta l'intero messaggio e non solo il corpo. Questi overload accettano anche un parametro maxSizeOfHeaders intero. Le intestazioni vengono sempre memorizzate nel buffer in memoria non appena viene creato il messaggio e questo parametro limita la quantità di memorizzazione nel buffer eseguita. È importante impostare questo parametro su un valore sicuro, se il codice XML proviene da un'origine non attendibile, per ridurre la possibilità di un attacco di tipo Denial of Service. Le versioni SOAP e WS-Addressing del messaggio rappresentato dal lettore XML devono corrispondere alle versioni indicate mediante il parametro di versione.

Creazione di messaggi con BodyWriter

Un overload CreateMessage accetta un'istanza di BodyWriter per descrivere il corpo del messaggio. BodyWriter è una classe astratta che può essere derivata per personalizzare la modalità di creazione dei corpi dei messaggi. È possibile creare una classe derivata BodyWriter personalizzata per descrivere i corpi dei messaggi nel modo desiderato. È necessario eseguire l'override del metodo BodyWriter.OnWriteBodyContents che accetta XmlDictionaryWriter. Questo metodo è responsabile della scrittura del corpo.

I body writer possono essere memorizzati nel buffer oppure no (trasmessi). I body writer memorizzati nel buffer possono scrivere il proprio contenuto qualsiasi numero di volte, quelli trasmessi possono invece scrivere il proprio contenuto una sola volta. La proprietà IsBuffered indica se un body writer è memorizzato o meno nel buffer. Per impostare questa proprietà per il body writer chiamare il costruttore BodyWriter protetto che accetta un parametro booleano isBuffered. I body writer supportano la creazione di un body writer memorizzato nel buffer da un body writer non memorizzato nel buffer. È possibile eseguire l'override del metodo OnCreateBufferedCopy per personalizzare questo processo. Per impostazione predefinita, viene utilizzato un buffer in memoria contenente il codice XML restituito da OnWriteBodyContents. OnCreateBufferedCopy accetta un parametro integer maxBufferSize. Se si esegue l'override di questo metodo, non è necessario creare buffer più grandi della dimensione massima specificata.

La classe BodyWriter fornisce i metodi WriteBodyContents e CreateBufferedCopy che sono essenzialmente thin wrapper rispettivamente dei metodi OnWriteBodyContents e OnCreateBufferedCopy. Questi metodi eseguono un controllo dello stato per assicurare che non sia possibile accedere a un body writer non memorizzato nel buffer più di una volta. I metodi vengono chiamati direttamente solo in caso di creazione di classi derivate Message personalizzate basate su BodyWriters.

Creazione di messaggi di errore

È possibile utilizzare determinati overload CreateMessage per creare messaggi di errore SOAP. Il più elementare accetta un oggetto MessageFault che descrive l'errore. Altri overload vengono forniti per comodità. Il primo di tali overload accetta un elemento FaultCode e una stringa motivo e crea un elemento MessageFault utilizzando MessageFault.CreateFault che utilizza queste informazioni. L'altro overload accetta un oggetto Detail e lo passa a CreateFault insieme al codice e al motivo dell'errore. Ad esempio, nell'operazione seguente viene restituito un errore.

Estrazione di dati del corpo del messaggio

La classe Message supporta più modalità di estrazione delle informazioni dal proprio corpo. Queste modalità possono essere classificate nelle categorie seguenti:

  • Recupero dell'intero corpo del messaggio scritto tutto in sola volta in un writer XML. Questa modalità viene definita scrittura di un messaggio.
  • Recupero di un lettore XML sul corpo del messaggio. Questo consente di accedere in seguito al corpo del messaggio un pezzo alla volta, come richiesto. Questa modalità viene definita lettura di un messaggio.
  • L'intero messaggio, incluso il corpo, può essere copiato in un buffer in memoria di tipo MessageBuffer. Questa modalità viene definita copia di un messaggio.

È possibile accedere al corpo di una classe Message una sola volta, indipendentemente dalla modalità di accesso. Un oggetto messaggio ha una proprietà State, impostata inizialmente su Creato. I tre metodi di accesso descritti nell'elenco precedente impostano lo stato rispettivamente su Scritto, Letto e Copiato. Inoltre, un metodo Close può impostare lo stato su Chiuso, quando il contenuto del corpo del messaggio non è più necessario. Il corpo del messaggio è accessibile solo nello stato Creato e non c'è alcun modo per tornare allo stato Creato dopo che è stato modificato.

Scrittura di messaggi

Il metodo WriteBodyContents scrive il contenuto del corpo di un'istanza di Message in un determinato writer XML. Il metodo WriteBody esegue la stessa operazione, ma racchiude il contenuto del corpo nell'elemento wrapper appropriato, ad esempio <soap:body>. Infine, WriteMessage scrive l'intero messaggio, incluse l'envelope SOAP di wrapping e le intestazioni. Se SOAP viene disattivato (la versione è MessageVersion.None), tutti e tre i metodi eseguono la stessa operazione, ovvero scrivono il contenuto del corpo del messaggio.

Ad esempio, nel codice seguente viene scritto il corpo di un messaggio in ingresso in un file.

Due metodi di supporto aggiuntivi scrivono determinati tag di elemento iniziali SOAP. Questi metodi non accedono al corpo del messaggio, pertanto non modificano lo stato del messaggio. Tra di essi sono inclusi:

  • WriteStartBody scrive l'elemento del corpo iniziale, ad esempio, <soap:Body>.
  • WriteStartEnvelope scrive l'elemento envelope iniziale, ad esempio, <soap:Envelope>.

Per scrivere i corrispondenti tag di elemento finali, chiamare WriteEndElement sul writer XML corrispondente. È raro che questi metodi vengano chiamati direttamente.

Lettura di messaggi

La modalità principale per leggere il corpo di un messaggio consiste nel chiamare GetReaderAtBodyContents. Si ottiene una classe XmlDictionaryReader utilizzabile per leggere il corpo del messaggio. Si noti che Message passa allo stato Letto non appena viene chiamato GetReaderAtBodyContents, non quando si utilizza il lettore XML restituito.

Il metodo GetBody consente anche di accedere al corpo del messaggio come oggetto tipizzato. Internamente, questo metodo utilizza GetReaderAtBodyContents e quindi esegue anche la transizione dello stato del messaggio allo stato Read (vedere la proprietà State).

È buona norma controllare la proprietà IsEmpty, caso in cui il corpo del messaggio è vuoto e GetReaderAtBodyContents genera una InvalidOperationException. Inoltre, se si tratta di un messaggio ricevuto (ad esempio una risposta), può essere necessario controllare anche IsFault, che indica se il messaggio contiene un errore.

L'overload più elementare di GetBody deserializza il corpo del messaggio in un'istanza di un tipo (indicato dal parametro generico) utilizzando un oggetto DataContractSerializer configurato con le impostazioni predefinite e con la quota MaxItemsInObjectGraph disabilitata. Se si desidera utilizzare un altro motore di serializzazione o configurare DataContractSerializer in modo non predefinito, utilizzare l'overload GetBody che accetta una classe XmlObjectSerializer.

Ad esempio, nel codice seguente vengono estratti i dati dal corpo di un messaggio che contiene un oggetto Person serializzato e viene stampato il nome della persona.

Copia di un messaggio in un buffer

Talvolta è necessario accedere al corpo del messaggio più di una volta, ad esempio per inoltrare lo stesso messaggio a più destinazioni come parte di un sistema di pubblicazione-sottoscrizione. In questo caso è necessario memorizzare nel buffer l'intero messaggio (incluso il corpo) in memoria. A questo scopo è possibile chiamare CreateBufferedCopy. Questo metodo accetta un parametro integer che rappresenta la dimensione massima del buffer e crea un buffer che non supera tale dimensione. È importante impostare il parametro su un valore sicuro, se il messaggio proviene da un'origine non attendibile.

Il buffer viene restituito come istanza di MessageBuffer. È possibile accedere ai dati nel buffer in diversi modi. Il modo principale consiste nel chiamare CreateMessage per creare istanze di Message dal buffer.

Un altro modo per accedere ai dati nel buffer consiste nell'implementare l'interfaccia IXPathNavigable implementata dalla classe MessageBuffer per accedere direttamente al codice XML sottostante. Alcuni overload di CreateNavigator consentono di creare strumenti di spostamento System.Xml.XPath protetti da una quota del nodo, limitando il numero di nodi XML che è possibile visitare. Questo consente di impedire attacchi di tipo Denial of Service basati su lunghi tempi di elaborazione. Questa quota è disattivata per impostazione predefinita. Alcuni overload CreateNavigator consentono di specificare la modalità di gestione dello spazio vuoto nel codice XML utilizzando l'enumerazione XmlSpace, con l'impostazione predefinita XmlSpace.None.

Un modo finale per accedere al contenuto di un buffer dei messaggi consiste nello scriverne il contenuto in un flusso utilizzando WriteMessage.

Nell'esempio seguente viene illustrato il processo di utilizzo di MessageBuffer: un messaggio in ingresso viene inoltrato a più destinatari e quindi registrato in un file. Senza la memorizzazione nel buffer, questa operazione non può essere eseguita, perché è possibile accedere al corpo del messaggio una sola volta.

La classe MessageBuffer ha altri membri non degni di nota. È possibile chiamare il metodo Close per liberare risorse quando il contenuto del buffer non è più necessario. La proprietà BufferSize restituisce la dimensione del buffer allocato. La proprietà MessageContentType restituisce il tipo di contenuti MIME del messaggio.

Accesso al corpo del messaggio a scopo di debug

A scopo di debug, è possibile chiamare il metodo ToString per ottenere una rappresentazione del messaggio come stringa. Questa rappresentazione corrisponde in genere all'aspetto che avrebbe il messaggio in transito se fosse codificato con il codificatore di testo, eccetto per il fatto che il codice XML sarebbe formattato in modo leggibile. L'unica eccezione è il corpo del messaggio. Il corpo può essere letto una sola volta e ToString non modifica lo stato del messaggio. Pertanto, il metodo ToString potrebbe non essere in grado di accedere al corpo e potrebbe sostituire un segnaposto, ad esempio, "..." o tre punti) invece del corpo del messaggio. Non utilizzare quindi ToString per registrare messaggi con contenuto del corpo importante.

Accesso ad altre parti del messaggio

Vengono fornite varie proprietà per accedere a informazioni sul messaggio diverse dal contenuto del corpo. Queste proprietà non possono tuttavia essere chiamate dopo la chiusura del messaggio:

  • La proprietà Headers rappresenta le intestazioni del messaggio. Vedere la sezione sull'utilizzo di intestazioni più avanti in questo argomento.
  • La proprietà Properties rappresenta le proprietà del messaggio, che sono pezzi di dati denominati collegati al messaggio che non vengono generalmente generati quando il messaggio viene inviato. Vedere la sezione sull'utilizzo di proprietà più avanti in questo argomento.
  • La proprietà Version indica la versione SOAP e WS-Addressing associata al messaggio o None se SOAP è disattivato.
  • La proprietà IsFault restituisce true se il messaggio e un messaggio di errore SOAP.
  • La proprietà IsEmpty restituisce true se il messaggio è vuoto.

È possibile utilizzare il metodo GetBodyAttribute per accedere a un particolare attributo sull'elemento wrapper del corpo, ad esempio, <soap:Body>, identificato da un nome e uno spazio dei nomi particolari. Se tale attributo non viene trovato, verrà restituito null. Questo metodo può essere chiamato solo quando Message si trova nello stato Creato, ovvero quando non si è ancora avuto accesso al corpo del messaggio.

Utilizzo di intestazioni

Una classe Message può contenere qualsiasi numero di frammenti XML denominati, definiti intestazioni. Ogni frammento è normalmente mappato a un'intestazione SOAP. Alle intestazioni si accede tramite la proprietà Headers di tipo MessageHeaders. MessageHeaders è un insieme di oggetti MessageHeaderInfo e le singole intestazioni sono accessibili tramite l'interfaccia IEnumerable o il relativo indicizzatore. Nel codice seguente vengono, ad esempio, elencati i nomi di tutte le intestazioni in una classe Message.

Aggiunta, rimozione e individuazione di intestazioni

È possibile aggiungere una nuova intestazione alla fine di tutte le intestazioni esistenti utilizzando il metodo Add. È possibile utilizzare il metodo Insert per inserire un'intestazione all'altezza di un particolare indice. Le intestazioni esistenti vengono spostate per adattare l'elemento inserito. Le intestazioni sono ordinate in base al proprio indice e il primo indice disponibile è 0. È possibile utilizzare i vari overload del metodo CopyHeadersFrom per aggiungere intestazioni da una diversa istanza di Message o MessageHeaders. Alcuni overload copiano una sola intestazione, mentre altri le copiano tutte. Il metodo Clear rimuove tutte le intestazioni. Il metodo RemoveAt rimuove un'intestazione all'altezza di un particolare indice, spostando tutte le intestazioni dopo di essa. Il metodo RemoveAll rimuove tutte le intestazioni con un nome e un spazio dei nomi particolari.

Recuperare una particolare intestazione utilizzando il metodo FindHeader. Questo metodo accetta il nome e lo spazio dei nomi dell'intestazione da cercare e restituisce l'indice. Se l'intestazione ricorre più di una volta, verrà generata un'eccezione. Se l'intestazione non viene trovata, verrà restituito -1.

Nel modello di intestazione SOAP, le intestazioni possono avere un valore Actor che specifica il destinatario desiderato dell'intestazione. L'overload FindHeader più elementare ricerca solo le intestazioni indirizzate al destinatario finale del messaggio. Tuttavia, un altro overload consente di specificare quali valori Actor sono inclusi nella ricerca. Per ulteriori informazioni, vedere la specifica SOAP.

Viene fornito un metodo CopyTo per copiare intestazioni da un insieme MessageHeaders a una matrice di oggetti MessageHeaderInfo.

Per accedere ai dati XML in un'intestazione è possibile chiamare GetReaderAtHeader e restituire un lettore XML per l'indice dell'intestazione specifico. Se si desidera deserializzare il contenuto dell'intestazione in un oggetto, utilizzare GetHeader o uno degli altri overload. Gli overload più elementari deserializzano le intestazioni tramite l'oggetto DataContractSerializer configurato in modalità predefinita. Se si desidera utilizzare un serializzatore diverso o una differente configurazione della classe DataContractSerializer, utilizzare uno degli overload che accettano un XmlObjectSerializer. Esistono anche overload che accettano il nome e lo spazio dei nomi dell'intestazione e, facoltativamente, un elenco di valori Actor invece di un indice; si tratta di una combinazione di FindHeader e GetHeader.

Utilizzo di proprietà

Un'istanza di Message può contenere un numero arbitrario di oggetti denominati di tipi arbitrari. Questo insieme è accessibile tramite la proprietà Properties di tipo MessageProperties. L'insieme implementa l'interfaccia IDictionary e agisce da mapping da String a Object. In genere, i valori delle proprietà non sono mappati direttamente a una parte del messaggio in transito, ma forniscono piuttosto diversi suggerimenti di elaborazione del messaggio ai vari canali nello stack di canali WCF o al framework di servizi CopyTo. Per un esempio, vedere Panoramica dell'architettura di trasferimento dei dati.

Eredità dalla classe Message

Se i tipi di messaggio incorporati creati utilizzando CreateMessage non soddisfano i requisiti, creare una classe che deriva dalla classe Message.

Definizione del contenuto del corpo del messaggio

Esistono tre tecniche principali per accedere ai dati all'interno del corpo di un messaggio: scrittura, lettura e copia in un buffer. Queste operazioni implicano, in ultima istanza, la chiamata dei metodi OnWriteBodyContents, OnGetReaderAtBodyContents e OnCreateBufferedCopy, rispettivamente, nella classe derivata di Message. La classe di base Message garantisce che venga chiamato uno solo di questi metodi, una sola volta, per ogni istanza di Message . La classe di base assicura inoltre che i metodi non vengano chiamati su un messaggio chiuso. Non c'è alcuna necessità di tenere traccia dello stato del messaggio nell'implementazione.

OnWriteBodyContents è un metodo astratto e deve essere implementato. L'uso di questo metodo è il modo più elementare per definire il contenuto del corpo del messaggio. Il messaggio seguente contiene, ad esempio, 100.000 numeri casuali da 1 a 20.

Per i metodi OnGetReaderAtBodyContents e OnCreateBufferedCopy sono disponibili implementazioni predefinite che funzionano nella maggior parte dei casi. Le implementazioni predefinite chiamano OnWriteBodyContents, memorizzano i risultati nel buffer e utilizzano il buffer prodotto. Tuttavia, in alcuni casi, questo potrebbe non essere sufficiente. Nell'esempio precedente, la lettura del messaggio comporta la memorizzazione nel buffer di 100.000 elementi XML, operazione che potrebbe non essere desiderabile. Potrebbe essere necessario eseguire l'override di OnGetReaderAtBodyContents per restituire una classe derivata XmlDictionaryReader personalizzata che fornisca numeri casuali. Sarà quindi possibile eseguire l'override di OnWriteBodyContents per utilizzare il lettore restituito dalla proprietà OnGetReaderAtBodyContents, come illustrato nell'esempio seguente.

Allo stesso modo, potrebbe essere necessario eseguire l'override di OnCreateBufferedCopy per restituire la classe derivata MessageBuffer personalizzata.

Oltre a fornire il contenuto del corpo del messaggio, la classe derivata del messaggio deve anche eseguire l'override delle proprietà Version, Headers e Properties.

Si noti che se si crea una copia di un messaggio, la copia utilizza le intestazioni del messaggio dell'originale.

Altri membri di cui è possibile eseguire l'override

È possibile eseguire l'override dei metodi OnWriteStartEnvelope, OnWriteStartHeaders e OnWriteStartBody per specificare come vengono scritti i tag iniziali degli elementi envelope SOAP, intestazioni SOAP e corpo SOAP. Questi normalmente corrispondono a <soap:Envelope>, **<soap:Header>**e <soap:Body>. In genere, questi metodi non scrivono alcun elemento se la proprietà Version restituisce MessageVersion.None.

Nota

L'implementazione predefinita di OnGetReaderAtBodyContents chiama OnWriteStartEnvelope e OnWriteStartBody prima di chiamare OnWriteBodyContents e memorizzare i risultati nel buffer. Le intestazioni non vengono scritte.

Eseguire l'override del metodo OnWriteMessage per modificare il modo in cui viene costruito l'intero messaggio dai diversi pezzi. Il metodo OnWriteMessage viene chiamato da WriteMessage e dall'implementazione di OnCreateBufferedCopy predefinita. Si noti che l'esecuzione dell'ovverride di WriteMessage non è una procedura consigliata. È preferibile eseguire l'override dei metodi On appropriati, ad esempio, OnWriteStartEnvelope, OnWriteStartHeaders e OnWriteBodyContents.

Eseguire l'override di OnBodyToString per ignorare come viene rappresentato il corpo del messaggio durante il debug. L'impostazione predefinita consiste nel rappresentarlo come tre punti ("..."). Si noti che questo metodo può essere chiamato più volte, quando lo stato del messaggio è diverso da Chiuso. Un'implementazione di questo metodo non dovrebbe mai provocare un'azione che deve essere eseguita una sola volta, ad esempio la lettura da un flusso forward-only.

Eseguire l'override del metodo OnGetBodyAttribute per consentire l'accesso agli attributi sull'elemento corpo SOAP. Questo metodo può essere chiamato il numero di volte desiderato, ma il tipo Message di base garantisce che venga chiamato solo quando il messaggio si trova nello stato Creato. Non è necessario per controllare lo stato in un'implementazione. L'implementazione predefinita restituisce sempre null, per indicare che non sono presenti attributi sull'elemento corpo.

Se l'oggetto Message deve eseguire particolari operazioni di pulitura quando il corpo del messaggio non è più necessario, è possibile eseguire l'override di OnClose. L'implementazione predefinita non esegue alcuna operazione.

Le proprietà IsEmpty e IsFault possono essere sottoposte a override. Per impostazione predefinita, entrambe restituiscono false.