Utilizzo della classe Message
La classe Message è fondamentale 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 usati 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 usando direttamente la classe Message. Ad esempio, può essere necessario usare 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 usare 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 usare la classe Message, acquisire familiarità con l'architettura di trasferimento dei dati WCF in Panoramica dell'architettura di trasferimento 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 usa SOAP, il corpo del messaggio viene mappato al corpo SOAP e le intestazioni vengono mappate alle intestazioni SOAP.
Uso della classe Message nelle operazioni
È possibile usare la classe Message come parametro di input e/o valore restituito di un'operazione. Se la classe Message viene usata in un punto qualsiasi di un'operazione, si applicano le restrizioni seguenti:
L'operazione non può avere qualsiasi parametro
out
oref
.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.
[ServiceContract]
public interface IMyService
{
[OperationContract]
Message GetData();
[OperationContract]
void PutData(Message m);
}
<ServiceContract()> _
Public Interface IMyService
<OperationContract()> _
Function GetData() As Message
<OperationContract()> _
Sub PutData(ByVal m As Message)
End Interface
Creazione di messaggi di base
La classe Message fornisce metodi factory CreateMessage
statici che è possibile usare 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 usare per il messaggio. Se si desidera usare le stesse versioni del protocollo del messaggio in ingresso, è possibile usare 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 usare 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. Usare la classe DataContractSerializer con le impostazioni predefinite per la serializzazione. Se si desidera usare un serializzatore diverso o si desidera che la classe DataContractSerializer
venga configurata in modo diverso, usare l'overload CreateMessage
che accetta anche un parametro XmlObjectSerializer
.
Per restituire un oggetto in un messaggio è, ad esempio, possibile usare il codice seguente.
public class MyService1 : IMyService
{
public Message GetData()
{
Person p = new Person();
p.name = "John Doe";
p.age = 42;
MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
return Message.CreateMessage(ver, "GetDataResponse", p);
}
public void PutData(Message m)
{
// Not implemented.
}
}
[DataContract]
public class Person
{
[DataMember] public string name;
[DataMember] public int age;
}
Public Class MyService1
Implements IMyService
Public Function GetData() As Message _
Implements IMyService.GetData
Dim p As New Person()
p.name = "John Doe"
p.age = 42
Dim ver As MessageVersion = _
OperationContext.Current.IncomingMessageVersion
Return Message.CreateMessage(ver, "GetDataResponse", p)
End Function
Public Sub PutData(ByVal m As Message) _
Implements IMyService.PutData
' Not implemented.
End Sub
End Class
<DataContract()> _
Public Class Person
<DataMember()> _
Public name As String
<DataMember()> _
Public age As Integer
End Class
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.
public class MyService2 : IMyService
{
public Message GetData()
{
FileStream stream = new FileStream("myfile.xml",FileMode.Open);
XmlDictionaryReader xdr =
XmlDictionaryReader.CreateTextReader(stream,
new XmlDictionaryReaderQuotas());
MessageVersion ver =
OperationContext.Current.IncomingMessageVersion;
return Message.CreateMessage(ver,"GetDataResponse",xdr);
}
public void PutData(Message m)
{
// Not implemented.
}
}
Public Class MyService2
Implements IMyService
Public Function GetData() As Message Implements IMyService.GetData
Dim stream As New FileStream("myfile.xml", FileMode.Open)
Dim xdr As XmlDictionaryReader = _
XmlDictionaryReader.CreateTextReader(stream, New XmlDictionaryReaderQuotas())
Dim ver As MessageVersion = OperationContext.Current.IncomingMessageVersion
Return Message.CreateMessage(ver, "GetDataResponse", xdr)
End Function
Public Sub PutData(ByVal m As Message) Implements IMyService.PutData
End Sub
End Class
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 usato un buffer in memoria contenente il codice XML restituito da OnWriteBodyContents
. OnCreateBufferedCopy
accetta un parametro di tipo 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 usare 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
usando MessageFault.CreateFault
che usa 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.
public class MyService3 : IMyService
{
public Message GetData()
{
FaultCode fc = new FaultCode("Receiver");
MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
return Message.CreateMessage(ver,fc,"Bad data","GetDataResponse");
}
public void PutData(Message m)
{
// Not implemented.
}
}
Public Class MyService3
Implements IMyService
Public Function GetData() As Message Implements IMyService.GetData
Dim fc As New FaultCode("Receiver")
Dim ver As MessageVersion = OperationContext.Current.IncomingMessageVersion
Return Message.CreateMessage(ver, fc, "Bad data", "GetDataResponse")
End Function
Public Sub PutData(ByVal m As Message) Implements IMyService.PutData
End Sub
End Class
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(XmlDictionaryWriter) 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 (Version è 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.
public class MyService4 : IMyService
{
public void PutData(Message m)
{
FileStream stream = new FileStream("myfile.xml",FileMode.Create);
XmlDictionaryWriter xdw =
XmlDictionaryWriter.CreateTextWriter(stream);
m.WriteBodyContents(xdw);
xdw.Flush();
}
public Message GetData()
{
throw new NotImplementedException();
}
}
Public Class MyService4
Implements IMyService
Public Sub PutData(ByVal m As Message) Implements IMyService.PutData
Dim stream As New FileStream("myfile.xml", FileMode.Create)
Dim xdw As XmlDictionaryWriter = XmlDictionaryWriter.CreateTextWriter(stream)
m.WriteBodyContents(xdw)
xdw.Flush()
End Sub
Public Function GetData() As Message Implements IMyService.GetData
Throw New NotImplementedException()
End Function
End Class
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 cui:
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 usabile per leggere il corpo del messaggio. Si noti che Message passa allo stato Letto non appena viene chiamato GetReaderAtBodyContents, non quando si usa il lettore XML restituito.
Il metodo GetBody consente anche di accedere al corpo del messaggio come oggetto tipizzato. Internamente, questo metodo usa 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) usando un oggetto DataContractSerializer configurato con le impostazioni predefinite e con la quota MaxItemsInObjectGraph disabilitata. Se si desidera usare un altro motore di serializzazione o configurare DataContractSerializer
in modo non predefinito, usare 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.
public class MyService5 : IMyService
{
public void PutData(Message m)
{
Person p = m.GetBody<Person>();
Console.WriteLine(p.name);
}
public Message GetData()
{
throw new NotImplementedException();
}
}
}
namespace Samples2
{
[ServiceContract]
public interface IMyService
{
[OperationContract]
Message GetData();
[OperationContract]
void PutData(Message m);
}
[DataContract]
public class Person
{
[DataMember] public string name;
[DataMember] public int age;
}
Public Class MyService5
Implements IMyService
Public Sub PutData(ByVal m As Message) Implements IMyService.PutData
Dim p As Person = m.GetBody(Of Person)()
Console.WriteLine(p.name)
End Sub
Public Function GetData() As Message Implements IMyService.GetData
Throw New NotImplementedException()
End Function
End Class
End Namespace
Namespace Samples2
<ServiceContract()> _
Public Interface IMyService
<OperationContract()> _
Function GetData() As Message
<OperationContract()> _
Sub PutData(ByVal m As Message)
End Interface
<DataContract()> _
Public Class Person
<DataMember()> _
Public name As String
<DataMember()> _
Public age As Integer
End Class
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(Int32). 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 usando 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 usando WriteMessage.
Nell'esempio seguente viene illustrato il processo d'uso 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.
[ServiceContract]
public class ForwardingService
{
private List<IOutputChannel> forwardingAddresses;
[OperationContract]
public void ForwardMessage (Message m)
{
//Copy the message to a buffer.
MessageBuffer mb = m.CreateBufferedCopy(65536);
//Forward to multiple recipients.
foreach (IOutputChannel channel in forwardingAddresses)
{
Message copy = mb.CreateMessage();
channel.Send(copy);
}
//Log to a file.
FileStream stream = new FileStream("log.xml",FileMode.Append);
mb.WriteMessage(stream);
stream.Flush();
}
}
<ServiceContract()> _
Public Class ForwardingService
Private forwardingAddresses As List(Of IOutputChannel)
<OperationContract()> _
Public Sub ForwardMessage(ByVal m As Message)
'Copy the message to a buffer.
Dim mb As MessageBuffer = m.CreateBufferedCopy(65536)
'Forward to multiple recipients.
Dim channel As IOutputChannel
For Each channel In forwardingAddresses
Dim copy As Message = mb.CreateMessage()
channel.Send(copy)
Next channel
'Log to a file.
Dim stream As New FileStream("log.xml", FileMode.Append)
mb.WriteMessage(stream)
stream.Flush()
End Sub
End Class
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 usare 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'uso 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'uso 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 usare il metodo GetBodyAttribute(String, String) 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.
Uso 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 è una raccolta di oggetti MessageHeaderInfo e le singole intestazioni sono accessibili tramite la relativa interfaccia IEnumerable o il relativo indicizzatore. Nel codice seguente vengono, ad esempio, elencati i nomi di tutte le intestazioni in una classe Message
.
public class MyService6 : IMyService
{
public void PutData(Message m)
{
foreach (MessageHeaderInfo mhi in m.Headers)
{
Console.WriteLine(mhi.Name);
}
}
public Message GetData()
{
throw new NotImplementedException();
}
}
Public Class MyService6
Implements IMyService
Public Sub PutData(ByVal m As Message) Implements IMyService.PutData
Dim mhi As MessageHeaderInfo
For Each mhi In m.Headers
Console.WriteLine(mhi.Name)
Next mhi
End Sub
Public Function GetData() As Message Implements IMyService.GetData
Throw New NotImplementedException()
End Function
End Class
Aggiunta, rimozione e individuazione di intestazioni
È possibile aggiungere una nuova intestazione alla fine di tutte le intestazioni esistenti usando il metodo Add. È possibile usare 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 vengono ordinate secondo il proprio indice e il primo indice disponibile è 0. È possibile usare i vari overload del metodo CopyHeadersFrom per aggiungere intestazioni da una diversa istanza di un oggetto 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 usando 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 altre informazioni, vedere la specifica SOAP.
Viene fornito un metodo CopyTo(MessageHeaderInfo[], Int32) per copiare intestazioni da una raccolta 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, usare GetHeader<T>(Int32) o uno degli altri overload. Gli overload più elementari deserializzano le intestazioni tramite l'oggetto DataContractSerializer configurato in modalità predefinita. Se si desidera usare un serializzatore diverso o una differente configurazione della classe DataContractSerializer
, usare 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
.
Uso di proprietà
Un'istanza di Message
può contenere un numero arbitrario di oggetti denominati di tipi arbitrari. Questa raccolta è accessibile tramite la proprietà Properties
di tipo MessageProperties
. La raccolta implementa l'interfaccia IDictionary<TKey,TValue> 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(MessageHeaderInfo[], Int32). Per un esempio, vedere la Panoramica dell'architettura di trasferimento dati.
Eredità dalla classe Message
Se i tipi di messaggio incorporati creati usando 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.
public class RandomMessage : Message
{
override protected void OnWriteBodyContents(XmlDictionaryWriter writer)
{
Random r = new Random();
for (int i = 0; i <100000; i++)
{
writer.WriteStartElement("number");
writer.WriteValue(r.Next(1,20));
writer.WriteEndElement();
}
}
//code omitted…
Public Class RandomMessage
Inherits Message
Protected Overrides Sub OnWriteBodyContents( _
ByVal writer As XmlDictionaryWriter)
Dim r As New Random()
Dim i As Integer
For i = 0 To 99999
writer.WriteStartElement("number")
writer.WriteValue(r.Next(1, 20))
writer.WriteEndElement()
Next i
End Sub
' Code omitted.
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 usano 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 usare il lettore restituito dal metodo OnGetReaderAtBodyContents(), come illustrato nell'esempio seguente.
public override MessageHeaders Headers
{
get { throw new Exception("The method or operation is not implemented."); }
}
public override MessageProperties Properties
{
get { throw new Exception("The method or operation is not implemented."); }
}
public override MessageVersion Version
{
get { throw new Exception("The method or operation is not implemented."); }
}
}
public class RandomMessage2 : Message
{
override protected XmlDictionaryReader OnGetReaderAtBodyContents()
{
return new RandomNumbersXmlReader();
}
override protected void OnWriteBodyContents(XmlDictionaryWriter writer)
{
XmlDictionaryReader xdr = OnGetReaderAtBodyContents();
writer.WriteNode(xdr, true);
}
public override MessageHeaders Headers
{
get { throw new Exception("The method or operation is not implemented."); }
}
public override MessageProperties Properties
{
get { throw new Exception("The method or operation is not implemented."); }
}
public override MessageVersion Version
{
get { throw new Exception("The method or operation is not implemented."); }
}
}
public class RandomNumbersXmlReader : XmlDictionaryReader
{
//code to serve up 100000 random numbers in XML form omitted…
Public Overrides ReadOnly Property Headers() As MessageHeaders
Get
Throw New Exception("The method or operation is not implemented.")
End Get
End Property
Public Overrides ReadOnly Property Properties() As MessageProperties
Get
Throw New Exception("The method or operation is not implemented.")
End Get
End Property
Public Overrides ReadOnly Property Version() As MessageVersion
Get
Throw New Exception("The method or operation is not implemented.")
End Get
End Property
End Class
Public Class RandomMessage2
Inherits Message
Protected Overrides Function OnGetReaderAtBodyContents() As XmlDictionaryReader
Return New RandomNumbersXmlReader()
End Function
Protected Overrides Sub OnWriteBodyContents(ByVal writer As XmlDictionaryWriter)
Dim xdr As XmlDictionaryReader = OnGetReaderAtBodyContents()
writer.WriteNode(xdr, True)
End Sub
Public Overrides ReadOnly Property Headers() As MessageHeaders
Get
Throw New Exception("The method or operation is not implemented.")
End Get
End Property
Public Overrides ReadOnly Property Properties() As MessageProperties
Get
Throw New Exception("The method or operation is not implemented.")
End Get
End Property
Public Overrides ReadOnly Property Version() As MessageVersion
Get
Throw New Exception("The method or operation is not implemented.")
End Get
End Property
End Class
Public Class RandomNumbersXmlReader
Inherits XmlDictionaryReader
'code to serve up 100000 random numbers in XML form omitted
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 usa 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 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
.