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 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.

[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.