Partager via


Vue d’ensemble de l’architecture de transfert de données

Windows Communication Foundation (WCF) peut être considéré comme une infrastructure de messagerie. Il peut recevoir des messages, les traiter et les distribuer au code utilisateur pour une action supplémentaire, ou il peut construire des messages à partir de données fournies par le code utilisateur et les remettre à une destination. Cette rubrique, destinée aux développeurs avancés, décrit l’architecture de gestion des messages et des données contenues. Pour obtenir une vue plus simple et orientée tâches de l’envoi et de la réception de données, consultez Spécification du transfert de données dans les contrats de service.

Remarque

Cette rubrique décrit les détails de l’implémentation WCF qui ne sont pas visibles en examinant le modèle objet WCF. Deux mots de prudence s’imposent en ce qui concerne les détails documentés de l’implémentation. Tout d’abord, les descriptions sont simplifiées ; l’implémentation réelle peut être plus complexe en raison d’optimisations ou d’autres raisons. Deuxièmement, vous ne devez jamais vous appuyer sur des détails d’implémentation spécifiques, même documentés, car ils peuvent changer sans préavis de version à version ou même dans une version de maintenance.

Architecture de base

Au cœur des fonctionnalités de gestion des messages WCF est la Message classe, qui est décrite en détail dans Utilisation de la classe de message. Les composants runtime de WCF peuvent être divisés en deux parties principales : la pile de canaux et l’infrastructure de service, avec la Message classe étant le point de connexion.

La pile de canaux est chargée de la conversion entre une instance valide Message et une action qui correspond à l’envoi ou à la réception de données de message. Côté envoi, la pile de canaux prend une instance valide Message et, après un traitement, effectue une action qui correspond logiquement à l’envoi du message. L’action peut envoyer des paquets TCP ou HTTP, mettre en file d’attente le message dans Message Queuing, écrire le message dans une base de données, l’enregistrer dans un partage de fichiers ou toute autre action, en fonction de l’implémentation. L’action la plus courante consiste à envoyer le message sur un protocole réseau. Côté réception, l’inverse se produit : une action est détectée (qui peut être des paquets TCP ou HTTP arrivant ou toute autre action) et, après traitement, la pile de canaux convertit cette action en une instance valide Message .

Vous pouvez utiliser WCF à l’aide de la Message classe et de la pile de canaux directement. Toutefois, cela est difficile et fastidieux. En outre, l’objet Message ne prend pas en charge les métadonnées. Vous ne pouvez donc pas générer de clients WCF fortement typés si vous utilisez WCF de cette façon.

Par conséquent, WCF inclut une infrastructure de service qui fournit un modèle de programmation facile à utiliser que vous pouvez utiliser pour construire et recevoir Message des objets. Le framework de service mappe les services aux types .NET Framework par le biais de la notion de contrats de service et distribue des messages aux opérations utilisateur qui sont simplement des méthodes .NET Framework marquées avec l’attribut OperationContractAttribute (pour plus d’informations, consultez Conception de contrats de service). Ces méthodes peuvent avoir des paramètres et des valeurs de retour. Côté service, l’infrastructure de service convertit les instances entrantes Message en paramètres et convertit les valeurs de retour en instances sortantes Message . Côté client, il fait l’inverse. Par exemple, considérez l’opération FindAirfare ci-dessous.

[ServiceContract]
public interface IAirfareFinderService
{
    [OperationContract]
    int FindAirfare(string FromCity, string ToCity, out bool IsDirectFlight);
}
<ServiceContract()> _
Public Interface IAirfareFinderService

    <OperationContract()> _
    Function FindAirfare(ByVal FromCity As String, _
    ByVal ToCity As String, ByRef IsDirectFlight As Boolean) As Integer

End Interface

Supposons que FindAirfare est appelé sur le client. L’infrastructure de service sur le client convertit les paramètres FromCity et ToCity en une instance sortante Message et la transmet à la pile de canaux pour qu'elle soit envoyée.

Côté service, lorsqu'une Message instance arrive de la pile de canaux, l'infrastructure de service extrait les données pertinentes du message pour remplir les paramètres FromCity et ToCity, puis appelle la méthode FindAirfare côté service. Lorsque la méthode est retournée, l’infrastructure de service prend la valeur entière retournée et le IsDirectFlight paramètre de sortie et crée une instance d’objet Message qui contient ces informations. Il transmet ensuite l’instance Message à la pile de canaux à renvoyer au client.

Côté client, une Message instance qui contient le message de réponse ressort de la pile de canaux. L’infrastructure de service extrait la valeur de retour et la valeur IsDirectFlight et les retourne à l’appelant du client.

Classe de message

La Message classe est destinée à être une représentation abstraite d’un message, mais sa conception est fortement liée au message SOAP. Un Message contient trois éléments d’informations majeurs : un corps de message, des en-têtes de message et des propriétés de message.

Corps du message

Le corps du message est destiné à représenter la charge utile de données réelle du message. Le corps du message est toujours représenté sous forme d’ensemble d’informations XML. Cela ne signifie pas que tous les messages créés ou reçus dans WCF doivent être au format XML. C'est à la pile de canaux qu'il revient de décider de l'interprétation du corps du message. Il peut l’émettre au format XML, le convertir en un autre format ou même l’omettre entièrement. Bien sûr, avec la plupart des liaisons que WCF fournit, le corps du message est représenté en tant que contenu XML dans la section corps d’une enveloppe SOAP.

Il est important de se rendre compte que la Message classe ne contient pas nécessairement de mémoire tampon avec des données XML représentant le corps. Contient logiquement Message un ensemble d’informations XML, mais cet Infoset peut être construit dynamiquement et n’existe peut-être jamais physiquement en mémoire.

Placer des données dans le corps du message

Il n’existe aucun mécanisme uniforme pour placer des données dans un corps de message. La Message classe a une méthode abstraite, OnWriteBodyContents(XmlDictionaryWriter)qui prend un XmlDictionaryWriter. Chaque sous-classe de la Message classe est chargée de remplacer cette méthode et d’écrire son propre contenu. Le corps du message contient logiquement l’ensemble d’informations XML qui OnWriteBodyContent produit. Par exemple, considérez la sous-classe suivante Message .

public class AirfareRequestMessage : Message
{
    public string fromCity = "Tokyo";
    public string toCity = "London";
    //code omitted…
    protected override void OnWriteBodyContents(XmlDictionaryWriter w)
    {
        w.WriteStartElement("airfareRequest");
        w.WriteElementString("from", fromCity);
        w.WriteElementString("to", toCity);
        w.WriteEndElement();
    }

    public override MessageVersion Version
    {
        get { throw new NotImplementedException("The method is not implemented.") ; }
    }

    public override MessageProperties Properties
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }
    public override MessageHeaders Headers
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override bool IsEmpty
    {
        get
        {
            return base.IsEmpty;
        }
    }

    public override bool IsFault
    {
        get
        {
            return base.IsFault;
        }
    }
}
Public Class AirfareRequestMessage
    Inherits Message

    Public fromCity As String = "Tokyo"
    Public toCity As String = "London"
    ' Code omitted…
    Protected Overrides Sub OnWriteBodyContents(ByVal w As XmlDictionaryWriter)
        w.WriteStartElement("airfareRequest")
        w.WriteElementString("from", fromCity)
        w.WriteElementString("to", toCity)
        w.WriteEndElement()
    End Sub

    Public Overrides ReadOnly Property Version() As MessageVersion
        Get
            Throw New NotImplementedException("The method 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 Headers() As MessageHeaders
        Get
            Throw New Exception("The method or operation is not implemented.")
        End Get
    End Property


    Public Overrides ReadOnly Property IsEmpty() As Boolean
        Get
            Return MyBase.IsEmpty
        End Get
    End Property

    Public Overrides ReadOnly Property IsFault() As Boolean
        Get
            Return MyBase.IsFault
        End Get
    End Property
End Class

Physiquement, une AirfareRequestMessage instance ne contient que deux chaînes (« fromCity » et « toCity »). Toutefois, logiquement, le message contient l’ensemble d’informations XML suivant :

<airfareRequest>
    <from>Tokyo</from>
    <to>London</to>
</airfareRequest>

Bien sûr, vous ne créez normalement pas de messages de cette façon, car vous pouvez utiliser l’infrastructure de service pour créer un message comme celui précédent à partir des paramètres de contrat d’opération. En outre, la Message classe a des méthodes statiques CreateMessage que vous pouvez utiliser pour créer des messages avec des types de contenu courants : un message vide, un message qui contient un objet sérialisé au format XML avec le DataContractSerializer, un message qui contient une erreur SOAP, un message qui contient du code XML représenté par un XmlReader, et ainsi de suite.

Obtention de données à partir d’un corps de message

Vous pouvez extraire les données stockées dans un corps de message de deux façons principales :

  • Vous pouvez obtenir l’intégralité du corps du message à la fois en appelant la WriteBodyContents(XmlDictionaryWriter) méthode et en transmettant un enregistreur XML. L'ensemble du corps du message est écrit dans cet enregistreur. L’obtention de l’intégralité du corps du message à la fois est également appelée écriture d’un message. L’écriture est effectuée principalement par la pile de canaux lors de l’envoi de messages : une partie de la pile de canaux obtient généralement l’accès à l’ensemble du corps du message, l’encode et l’envoie.

  • Une autre façon d’extraire les informations du corps du message consiste à appeler GetReaderAtBodyContents() et à obtenir un lecteur XML. Le corps du message est ensuite accessible de manière séquentielle selon les besoins en appelant des méthodes sur le lecteur. La découverte par morceaux du corps du message est également appelée lecture d’un message. La lecture du message est principalement utilisée par l’infrastructure de service lors de la réception de messages. Par exemple, lorsque le service DataContractSerializer est en cours d’utilisation, l’infrastructure de service obtient un lecteur XML sur le corps et le transmet au moteur de désérialisation, qui commence ensuite à lire le message élément par élément et à construire le graphe d'objets correspondant.

Un corps de message ne peut être récupéré qu’une seule fois. Cela permet d'utiliser des flux orientés vers l'avant seulement. Par exemple, vous pouvez écrire une substitution OnWriteBodyContents(XmlDictionaryWriter) que lit à partir de FileStream et retourne les résultats sous forme d'un ensemble d'informations XML. Vous n'aurez jamais besoin de revenir au début du document.

Les méthodes WriteBodyContents et GetReaderAtBodyContents vérifient simplement que le corps du message n’a jamais été récupéré auparavant, puis appellent OnWriteBodyContents ou OnGetReaderAtBodyContents, respectivement.

Utilisation des messages dans WCF

La plupart des messages peuvent être classés comme sortants (ceux créés par l’infrastructure de service à envoyer par la pile de canaux) ou entrants (ceux qui arrivent de la pile de canaux et sont interprétés par l’infrastructure de service). En outre, la pile de canaux peut fonctionner en mode de mise en mémoire tampon ou de diffusion en continu. L’infrastructure de service peut également exposer un modèle de programmation diffusé en continu ou non. Cela conduit aux cas répertoriés dans le tableau suivant, ainsi qu’aux détails simplifiés de leur implémentation.

Type de message Données relatives au corps du message Implémentation de l'écriture (OnWriteBodyContents) Implémentation de la lecture (OnGetReaderAtBodyContents)
Sortant, créé à partir du modèle de programmation sans diffusion en continu Données nécessaires à la rédaction du message (par exemple, un objet et l’instance nécessaires à sa sérialisation)* Logique personnalisée pour écrire le message en fonction des données stockées (par exemple, appeler WriteObject sur DataContractSerializer si c'est le sérialiseur utilisé) Appeler OnWriteBodyContents, mettre en mémoire tampon les résultats, retourner un lecteur XML sur la mémoire tampon
Sortant, créé à partir du modèle de programmation avec diffusion en continu Stream dans lequel les données doivent être écrites* Écrire des données à partir du flux stocké à l’aide du IStreamProvider mécanisme* Appeler OnWriteBodyContents, mettre en mémoire tampon les résultats, retourner un lecteur XML sur la mémoire tampon
Entrant, provenant de la pile de canaux de diffusion en continu Un objet Stream qui représente les données entrant sur le réseau, surmonté par un XmlReader Écrire le contenu à partir du contenu stocké XmlReader à l’aide de WriteNode Retourne le XmlReaderstocké
Entrant, provenant de la pile de canaux sans diffusion en continu Mémoire tampon qui contient les données relatives au corps avec un XmlReader placé sur celle-ci Écrit le contenu à partir du contenu stocké XmlReader à l’aide de WriteNode Retourne le langage stocké

* Ces éléments ne sont pas implémentés directement dans Message les sous-classes, mais dans les sous-classes de la BodyWriter classe. Pour plus d’informations sur la BodyWriterclasse de messages, consultez Utilisation de la classe de messages.

En-têtes de message

Un message peut contenir des en-têtes. Un en-tête se compose logiquement d’un ensemble d’informations XML associé à un nom, à un espace de noms et à quelques autres propriétés. Les en-têtes de message sont accessibles à l’aide de la Headers propriété sur Message. Chaque en-tête est représenté par une MessageHeader classe. Normalement, les en-têtes de message sont mappés aux en-têtes de message SOAP lors de l’utilisation d’une pile de canaux configurée pour fonctionner avec des messages SOAP.

Le fait de placer des informations dans un en-tête de message et d’extraire des informations de celui-ci est similaire à l’utilisation du corps du message. Le processus est quelque peu simplifié, car la diffusion en continu n’est pas prise en charge. Il est possible d’accéder au contenu du même en-tête plusieurs fois, et les en-têtes sont accessibles dans un ordre arbitraire, forçant les en-têtes à toujours être mis en mémoire tampon. Aucun mécanisme à usage général n’est disponible pour obtenir un lecteur XML sur un en-tête, mais il existe une MessageHeader sous-classe interne à WCF qui représente un en-tête lisible avec une telle fonctionnalité. Ce type de MessageHeader est créé par la pile de canaux lors de la réception d'un message avec en-têtes d'application personnalisés. Cela permet à l’infrastructure de service d’utiliser un moteur de désérialisation, tel que le DataContractSerializer, pour interpréter ces en-têtes.

Pour plus d’informations, consultez Utilisation de la classe de messages.

Propriétés de message

Un message peut contenir des propriétés. Une propriété est n’importe quel objet .NET Framework associé à un nom de chaîne. Les propriétés sont accessibles via la Properties propriété sur Message.

Contrairement au corps du message et aux en-têtes de message (qui correspondent normalement au corps SOAP et aux en-têtes SOAP, respectivement), les propriétés de message ne sont normalement pas envoyées ou reçues avec les messages. Les propriétés de message existent principalement en tant que mécanisme de communication pour transmettre des données sur le message entre les différents canaux de la pile de canaux et entre la pile de canaux et le modèle de service.

Par exemple, le canal de transport HTTP inclus dans le cadre de WCF est capable de produire différents codes d’état HTTP, tels que « 404 (introuvable) » et « 500 (erreur de serveur interne) » lorsqu’il envoie des réponses aux clients. Avant d’envoyer un message de réponse, il vérifie si le Properties du Message contient une propriété appelée « httpResponse » qui contient un objet de type HttpResponseMessageProperty. Si une telle propriété est trouvée, elle examine la StatusCode propriété et utilise ce code d’état. S’il est introuvable, le code « 200 (OK) » par défaut est utilisé.

Pour plus d’informations, consultez Utilisation de la classe de messages.

Le message dans son ensemble

Jusqu’à présent, nous avons discuté des méthodes d’accès aux différentes parties du message en isolation. Toutefois, la Message classe fournit également des méthodes permettant d’utiliser l’ensemble du message dans son ensemble. Par exemple, la WriteMessage méthode écrit l’intégralité du message dans un enregistreur XML.

Pour que cela soit possible, un mappage doit être défini entre l’instance entière Message et un ensemble d’informations XML. Un tel mappage existe en fait : WCF utilise la norme SOAP pour définir ce mappage. Lorsqu’une Message instance est écrite en tant qu’ensemble d’informations XML, l’ensemble d’informations résultant est l’enveloppe SOAP valide qui contient le message. Ainsi, WriteMessage procédez normalement comme suit :

  1. Écrivez la balise d’ouverture de l’élément d’enveloppe SOAP.

  2. Rédigez la balise d'ouverture de l'élément d'en-tête de SOAP, énumérez tous les en-têtes et fermez l'élément d'en-tête.

  3. Écrire la balise d'ouverture de l'élément de corps SOAP.

  4. Appelez WriteBodyContents ou une méthode équivalente pour écrire le contenu.

  5. Fermer les éléments de corps et d'enveloppe.

Les étapes précédentes sont étroitement liées à la norme SOAP. Cela est compliqué par le fait que plusieurs versions de SOAP existent, par exemple, il est impossible d’écrire correctement l’élément d’enveloppe SOAP sans connaître la version SOAP en cours d’utilisation. En outre, dans certains cas, il peut être souhaitable de désactiver complètement ce mappage complexe spécifique à SOAP.

À ces fins, une Version propriété est fournie sur Message. Il peut être défini sur la version SOAP à utiliser lors de l’écriture du message, ou sur None pour empêcher les mappages spécifiques à SOAP. Si la propriété Version est définie sur None, les méthodes qui fonctionnent avec l’ensemble du message agissent comme si le message était constitué de son corps uniquement, par exemple, WriteMessage appellerait simplement WriteBodyContents au lieu d’effectuer les étapes multiples répertoriées ci-dessus. Il est attendu que sur les messages entrants, Version soit détecté automatiquement et défini correctement.

Pile de canaux

Canaux

Comme indiqué précédemment, la pile de canaux est chargée de convertir des instances sortantes Message en une action (par exemple, l’envoi de paquets sur le réseau) ou la conversion d’une action (par exemple, la réception de paquets réseau) en instances entrantes Message .

La pile de canaux est composée d’un ou plusieurs canaux ordonnés dans une séquence. Une instance sortante Message est passée au premier canal de la pile (également appelé canal le plus haut), qui le transmet au canal suivant dans la pile, et ainsi de suite. Le message se termine dans le dernier canal, appelé canal de transport. Les messages entrants proviennent du canal de transport et sont transmis de canal en canal le long de la pile. À partir du canal le plus haut, le message est généralement passé dans l’infrastructure de service. Bien qu’il s’agisse du modèle habituel pour les messages d’application, certains canaux peuvent fonctionner légèrement différemment, par exemple, ils peuvent envoyer leurs propres messages d’infrastructure sans être transmis à partir d’un canal ci-dessus.

Les canaux peuvent agir sur le message de différentes façons à mesure qu'il passe par l'ensemble. L’opération la plus courante consiste à ajouter un en-tête à un message sortant et à lire des en-têtes sur un message entrant. Par exemple, un canal peut calculer la signature numérique d’un message et l’ajouter en tant qu’en-tête. Un canal peut également inspecter cet en-tête de signature numérique sur les messages entrants et bloquer ceux qui n'ont pas de signature valide, les empêchant ainsi de progresser dans la pile de canaux. Les canaux définissent également ou inspectent souvent les propriétés du message. Le corps du message n’est généralement pas modifié, même s’il est autorisé, par exemple, le canal de sécurité WCF peut chiffrer le corps du message.

Canaux de transport et encodeurs de messages

Le canal le plus bas de la pile est responsable de la transformation réelle d’un canal sortant Message, tel que modifié par d’autres canaux, en une action. Côté réception, c'est le canal qui convertit une action en Message que d'autres canaux traitent.

Comme indiqué précédemment, les actions peuvent être variées : l’envoi ou la réception de paquets réseau via différents protocoles, la lecture ou l’écriture du message dans une base de données, ou la mise en file d’attente ou le retrait du message dans une file d’attente Message Queuing, pour ne citer que quelques exemples. Toutes ces actions ont une chose en commun : elles nécessitent une transformation entre l’instance WCFMessage et un groupe réel d’octets qui peuvent être envoyés, reçus, lus, écrits, mis en file d’attente ou retirés de la file d’attente. Le processus de conversion d’un Message groupe d’octets est appelé encodage, et le processus inverse de création d’un Message groupe d’octets est appelé décodage.

La plupart des canaux de transport utilisent des composants appelés encodeurs de messages pour accomplir le travail d’encodage et de décodage. Un encodeur de message est une sous-classe de la MessageEncoder classe. MessageEncoder inclut différentes surcharges de méthode ReadMessage et WriteMessage pour effectuer la conversion entre Message et des groupes d'octets.

Côté envoi, un canal de transport de mise en mémoire tampon transmet l’objet Message qu’il a reçu d’un canal situé au-dessus à WriteMessage. Il récupère un tableau d’octets, qu’il utilise ensuite pour effectuer son action (par exemple, empaqueter ces octets en tant que paquets TCP valides et les envoyer à la destination correcte). Un canal de transport de diffusion en continu crée d'abord un Stream (par exemple, sur la connexion TCP sortante), puis passe à la fois le Stream et le Message qu'il doit envoyer à la surcharge WriteMessage appropriée, qui écrit le message.

Du côté réception, un canal de transport de mise en mémoire tampon extrait les octets entrants (provenant par exemple des paquets TCP entrants) dans un tableau et appelle ReadMessage pour obtenir un objet Message qu'il peut remonter plus haut dans la pile de canaux. Un canal de transport de streaming crée un Stream objet (par exemple, un flux réseau via la connexion TCP entrante) et le transmet à ReadMessage pour récupérer un Message objet.

La séparation entre les canaux de transport et l’encodeur de message n’est pas obligatoire ; il est possible d’écrire un canal de transport qui n’utilise pas d’encodeur de message. Toutefois, l’avantage de cette séparation est une facilité de composition. Tant qu’un canal de transport utilise uniquement la base MessageEncoder, il peut fonctionner avec n’importe quel encodeur de message WCF ou tiers. De même, le même encodeur peut normalement être utilisé dans n’importe quel canal de transport.

Opération d’encodeur de message

Pour décrire l’opération classique d’un encodeur, il est utile de prendre en compte les quatre cas suivants.

Opération Commentaire
Encodage, mise en mémoire tampon En mode mis en mémoire tampon, l’encodeur crée normalement une mémoire tampon de taille variable, puis crée un enregistreur XML sur celui-ci. Il appelle ensuite WriteMessage(XmlWriter) sur le message encodé, qui effectue la rédaction des en-têtes puis du corps à l'aide de WriteBodyContents(XmlDictionaryWriter), comme expliqué dans la section précédente concernant Message de cette rubrique. Le contenu de la mémoire tampon (représenté sous la forme d’un tableau d’octets) est ensuite retourné pour que le canal de transport soit utilisé.
Encodage, avec diffusion en continu En mode streamé, l’opération est similaire à celle ci-dessus, mais plus simple. Il n’est pas nécessaire de disposer d’une mémoire tampon. Un enregistreur XML est normalement créé sur le flux et WriteMessage(XmlWriter) est appelé sur Message pour l'écrire dans cet enregistreur.
Décodage, mise en mémoire tampon Lors du décodage en mode mis en mémoire tampon, une sous-classe spéciale Message qui contient les données mises en mémoire tampon est normalement créée. Les en-têtes du message sont lus et un lecteur XML positionné sur le corps du message est créé. Il s'agit du lecteur qui sera retourné avec GetReaderAtBodyContents().
Décodage, avec diffusion en continu Lors du décodage en mode streamé, une sous-classe de message spéciale est normalement créée. Le flux est suffisamment avancé pour lire tous les en-têtes et le positionner sur le corps du message. Un lecteur XML est ensuite créé sur le flux. Il s'agit du lecteur qui sera retourné avec GetReaderAtBodyContents().

Les encodeurs peuvent également effectuer d’autres fonctions. Par exemple, les encodeurs peuvent regrouper des lecteurs et des enregistreurs XML. Il est coûteux de créer un lecteur ou un enregistreur XML chaque fois qu’un lecteur est nécessaire. Par conséquent, les encodeurs gèrent normalement un pool de lecteurs et un pool d’enregistreurs de taille configurable. Dans les descriptions de l’opération d’encodeur décrites précédemment, chaque fois que l’expression « créer un lecteur/enregistreur XML » est utilisée, cela signifie normalement « en prendre un à partir du pool ou en créer un si un fichier n’est pas disponible ». L’encodeur (et la Message sous-classe qu’il crée lors du décodage) contiennent la logique permettant de renvoyer des lecteurs et des enregistreurs aux pools une fois qu’ils ne sont plus nécessaires (par exemple, lorsque la Message fermeture est terminée).

WCF fournit trois encodeurs de messages, bien qu’il soit possible de créer des types personnalisés supplémentaires. Les types fournis sont Text, Binary et Message Transmission Optimization Mechanism (MTOM). Ces informations sont décrites en détail dans le choix d’un encodeur de message.

Interface IStreamProvider

Lors de l'écriture d'un message sortant contenant un corps avec diffusion en continu vers un enregistreur XML, Message utilise une séquence d'appels similaire à la suivante dans son implémentation OnWriteBodyContents(XmlDictionaryWriter) :

  • Écrivez toutes les informations nécessaires précédant le flux (par exemple, la balise XML ouvrante).

  • Écrivez le flux de données.

  • Écrivez toutes les informations en suivant le flux (par exemple, la balise XML fermante).

Cela fonctionne bien avec les encodages similaires à l’encodage XML textuel. Toutefois, certains encodages ne placent pas les informations d’ensemble d’informations XML (par exemple, les balises pour les éléments XML de début et de fin) ainsi que les données contenues dans les éléments. Par exemple, dans l’encodage MTOM, le message est divisé en plusieurs parties. Une partie contient l’ensemble d’informations XML, qui peut contenir des références à d’autres parties pour le contenu réel de l’élément. La taille de l'ensemble d'informations XML étant normalement réduite par rapport à celle du contenu avec diffusion en continu, il est donc logique de mettre cet ensemble en mémoire tampon, de l'écrire, puis d'écrire le contenu avec diffusion en continu. Cela signifie que lorsque la balise d'élément de fermeture est écrite, le flux ne doit pas encore avoir été écrit.

À cet effet, l’interface IStreamProvider est utilisée. L’interface a une GetStream() méthode qui retourne le flux à écrire. La bonne façon d’écrire un corps de message transmis en continu dans OnWriteBodyContents(XmlDictionaryWriter) est la suivante :

  1. Écrivez toutes les informations nécessaires précédant le flux (par exemple, la balise XML ouvrante).

  2. Appelez la surcharge WriteValue sur le XmlDictionaryWriter qui accepte IStreamProvider, avec une implémentation IStreamProvider qui retourne le flux à écrire.

  3. Écrivez toutes les informations en suivant le flux (par exemple, la balise XML fermante).

Avec cette approche, l'enregistreur XML peut choisir à quel moment appeler GetStream() et écrire les données avec diffusion en continu. Par exemple, les enregistreurs XML textuels et binaires l’appellent immédiatement et écrivent le contenu diffusé entre les balises de début et de fin. L'enregistreur MTOM peut décider d'appeler GetStream() ultérieurement, lorsqu'il est prêt à écrire la partie appropriée du message.

Représentation des données dans Service Framework

Comme indiqué dans la section « Architecture de base » de cette rubrique, l’infrastructure de service fait partie de WCF qui, entre autres, est responsable de la conversion entre un modèle de programmation convivial pour les données de message et les instances réelles Message . Normalement, un échange de messages est représenté dans l’infrastructure de service en tant que méthode .NET Framework marquée avec l’attribut OperationContractAttribute . La méthode peut prendre certains paramètres et retourner une valeur de retour ou des paramètres sortants (ou les deux). Côté service, les paramètres d’entrée représentent le message entrant, et les paramètres de retour et de sortie représentent le message sortant. Côté client, l’inverse est vrai. Le modèle de programmation pour décrire les messages à l’aide de paramètres et la valeur de retour est décrit en détail dans La spécification du transfert de données dans les contrats de service. Toutefois, cette section fournit une brève vue d’ensemble.

Modèles de programmation

L’infrastructure de service WCF prend en charge cinq modèles de programmation différents pour décrire les messages :

1. Message vide

C’est le cas le plus simple. Pour décrire un message entrant vide, n’utilisez aucun paramètre d’entrée.

[OperationContract]
int GetCurrentTemperature();
<OperationContract()> Function GetCurrentTemperature() As Integer

Pour décrire un message sortant vide, utilisez une valeur de retour void et n’utilisez aucun paramètre out :

[OperationContract]
void SetDesiredTemperature(int t);
<OperationContract()> Sub SetDesiredTemperature(ByVal t As Integer)

Notez que cela diffère d’un contrat d’opération unidirectionnel :

[OperationContract(IsOneWay = true)]
void SetLightbulb(bool isOn);
<OperationContract(IsOneWay:=True)> Sub SetLightbulb(ByVal isOn As Boolean)

Dans l’exemple SetDesiredTemperature , un modèle d’échange de messages bidirectionnel est décrit. Un message est retourné à partir de l’opération, mais il est vide. Il est possible de retourner une erreur à partir de l'opération. Dans l’exemple « Définir lightbulb », le modèle d’échange de messages est unidirectionnel. Il n’existe donc aucun message sortant à décrire. Le service ne peut communiquer aucun état au client dans ce cas.

2. Utilisation directe de la classe de message

Il est possible d’utiliser la Message classe (ou l’une de ses sous-classes) directement dans un contrat d’opération. Dans ce cas, l’infrastructure de service se contente de transmettre le Message de l’opération à la pile de canaux et vice versa, sans traitement supplémentaire.

Il existe deux cas d’usage principaux pour l’utilisation directe de Message. Vous pouvez l’utiliser pour les scénarios avancés, quand aucun des autres modèles de programmation ne vous donne suffisamment de flexibilité pour décrire votre message. Par exemple, vous pouvez utiliser des fichiers sur le disque pour décrire un message, avec les propriétés du fichier devenant des en-têtes de message et le contenu du fichier devenant le corps du message. Vous pouvez ensuite créer quelque chose de similaire à ce qui suit.

public class FileMessage : Message
// Code not shown.
Public Class FileMessage
    Inherits Message
    ' Code not shown.

La deuxième utilisation courante dans Message un contrat d’opération est lorsqu’un service ne s’intéresse pas au contenu particulier du message et agit sur le message comme sur une zone noire. Par exemple, vous pouvez avoir un service qui transfère les messages à plusieurs autres destinataires. Le contrat peut être écrit comme suit.

[OperationContract]
public FileMessage GetFile()
{
    //code omitted…
    FileMessage fm = new FileMessage("myFile.xml");
    return fm;
}
<OperationContract()> Public Function GetFile() As FileMessage
    'code omitted…
    Dim fm As New FileMessage("myFile.xml")
    Return fm
End Function

La ligne Action ="*" désactive la distribution des messages et s’assure que tous les messages envoyés au contrat IForwardingService font place à l’opération ForwardMessage . (Normalement, le répartiteur examine l’en-tête « Action » du message pour déterminer l’opération à laquelle il est destiné. Action="* » signifie « toutes les valeurs possibles de l’en-tête d’action ».) La combinaison d’Action="* » et l’utilisation de Message en tant que paramètre est appelée « contrat universel », car elle est en mesure de recevoir tous les messages possibles. Pour pouvoir envoyer tous les messages possibles, utilisez Message comme valeur de retour et définissez ReplyAction sur « * ». Cela empêchera le framework de service d’ajouter son propre en-tête Action, vous permettant ainsi de contrôler cet en-tête grâce à l’objet Message que vous renvoyez.

3. Contrats de message

WCF fournit un modèle de programmation déclaratif pour décrire les messages, appelés contrats de message. Ce modèle est décrit en détail dans Utilisation des contrats de message. Essentiellement, l’ensemble du message est représenté par un seul type .NET Framework qui utilise des attributs comme le MessageBodyMemberAttribute et MessageHeaderAttribute pour décrire les parties de la classe de contrat de message à mapper à quelle partie du message.

Les contrats de message fournissent beaucoup de contrôle sur les instances résultantes Message (bien que évidemment pas autant de contrôle que l’utilisation directe de la Message classe). Par exemple, les corps de message sont souvent composés de plusieurs éléments d’informations, chacun représenté par son propre élément XML. Ces éléments peuvent se produire directement dans le corps (mode nu ) ou être encapsulés dans un élément XML englobant. Le modèle de programmation de contrat de message vous permet de prendre une décision de type « nu ou encapsulé » et de contrôler le nom de wrapper et d'espace de noms.

L’exemple de code suivant d’un contrat de message illustre ces fonctionnalités.

[MessageContract(IsWrapped = true, WrapperName = "Order")]
public class SubmitOrderMessage
{
    [MessageHeader]
    public string customerID;
    [MessageBodyMember]
    public string item;
    [MessageBodyMember]
    public int quantity;
}
<MessageContract(IsWrapped:=True, WrapperName:="Order")> _
Public Class SubmitOrderMessage
    <MessageHeader()> Public customerID As String
    <MessageBodyMember()> Public item As String
    <MessageBodyMember()> Public quantity As Integer
End Class

Les éléments marqués pour être sérialisés (avec le MessageBodyMemberAttribute, MessageHeaderAttributeou d’autres attributs associés) doivent être sérialisables pour participer à un contrat de message. Pour plus d’informations, consultez la section « Sérialisation » plus loin dans cette rubrique.

4. Paramètres

Souvent, un développeur qui souhaite décrire une opération qui agit sur plusieurs éléments de données n’a pas besoin du degré de contrôle que les contrats de message fournissent. Par exemple, lors de la création de nouveaux services, on ne souhaite généralement pas décider entre un message brut ou enveloppé ni choisir le nom de l’élément enveloppe. La prise de ces décisions nécessite souvent une connaissance approfondie des services Web et soap.

L’infrastructure de service WCF peut choisir automatiquement la meilleure représentation SOAP interopérable pour l’envoi ou la réception de plusieurs informations associées, sans forcer ces choix sur l’utilisateur. Pour ce faire, il suffit de décrire ces informations en tant que paramètres ou valeurs de retour d’un contrat d’opération. Par exemple, considérez le contrat d’opération suivant.

[OperationContract]
void SubmitOrder(string customerID, string item, int quantity);
<OperationContract()> _
Sub SubmitOrder( _
    ByVal customerID As String, _
    ByVal item As String, _
    ByVal quantity As Integer)

L’infrastructure de service décide automatiquement de placer les trois éléments d’informations (customerIDet itemquantity) dans le corps du message et de les encapsuler dans un élément wrapper nommé SubmitOrderRequest.

La description des informations à envoyer ou à recevoir sous la forme d’une liste simple de paramètres de contrat d’opération est l’approche recommandée, sauf si des raisons particulières existent pour passer au contrat de message plus complexe ou Messageaux modèles de programmation basés sur des données.

5. Flux de données

L’utilisation Stream ou l’une de ses sous-classes dans un contrat d’opération ou en tant que seul corps de message d’un contrat de message peut être considérée comme un modèle de programmation distinct des modèles de programmation décrits ci-dessus. Utiliser Stream de cette manière est le seul moyen de garantir que votre contrat sera utilisable en mode diffusé, à moins d’écrire votre propre sous-classe Message compatible avec la diffusion. Pour plus d’informations, consultez Données volumineuses et diffusion en continu.

Quand Stream ou l’une de ses sous-classes est utilisée de cette façon, le sérialiseur n’est pas appelé. Pour les messages sortants, une sous-classe de diffusion en continu Message spéciale est créée et le flux est écrit comme décrit dans la section de l’interface IStreamProvider . Pour les messages entrants, l’infrastructure de service crée une Stream sous-classe sur le message entrant et la fournit à l’opération.

Restrictions du modèle de programmation

Les modèles de programmation décrits ci-dessus ne peuvent pas être combinés arbitrairement. Par exemple, si une opération accepte un type de contrat de message, le contrat de message doit être son seul paramètre d’entrée. Par ailleurs, l’opération doit ensuite retourner un message vide (type de retour 'void') ou un autre contrat de message. Ces restrictions de modèle de programmation sont décrites dans les rubriques de chaque modèle de programmation spécifique : utilisation des contrats de message, utilisation de la classe de messages et données volumineuses et diffusion en continu.

Formateurs de message

Les modèles de programmation décrits ci-dessus sont pris en charge en branchant des composants appelés formateurs de messages dans l’infrastructure de service. Les formateurs de messages sont des types qui implémentent l’interface IClientMessageFormatter ou IDispatchMessageFormatter (ou les deux) afin de les utiliser dans les clients et les clients de service WCF, respectivement.

Les formateurs de messages sont normalement branchés par les comportements. Par exemple, DataContractSerializerOperationBehavior branche le formateur de messages de contrat de données. Cette opération est effectuée côté service en définissant Formatter sur le formateur correct dans la ApplyDispatchBehavior(OperationDescription, DispatchOperation) méthode, ou sur le côté client en définissant Formatter le formateur correct dans la ApplyClientBehavior(OperationDescription, ClientOperation) méthode.

Les tableaux suivants répertorient les méthodes qu’un formateur de message peut implémenter.

Interface Méthode Action
IDispatchMessageFormatter DeserializeRequest(Message, Object[]) Convertit un paramètre entrant Message en paramètres d’opération
IDispatchMessageFormatter SerializeReply(MessageVersion, Object[], Object) Crée un Message sortant à partir de la valeur de retour/des paramètres de sortie d'opération
IClientMessageFormatter SerializeRequest(MessageVersion, Object[]) Crée une sortie Message à partir des paramètres de l’opération
IClientMessageFormatter DeserializeReply(Message, Object[]) Convertit une valeur entrante Message en valeur de retour/paramètres en sortie

Sérialisation

Chaque fois que vous utilisez des contrats de message ou des paramètres pour décrire le contenu du message, vous devez utiliser la sérialisation pour effectuer la conversion entre les types .NET Framework et la représentation XML Infoset. La sérialisation est utilisée à d’autres endroits dans WCF, par exemple, Message a une méthode générique GetBody que vous pouvez utiliser pour lire l’intégralité du corps du message désérialisé dans un objet.

WCF prend en charge deux technologies de sérialisation « prêtes à l’emploi » pour la sérialisation et la désérialisation des paramètres et des parties de message : le DataContractSerializer et le XmlSerializer. En outre, vous pouvez écrire des sérialiseurs personnalisés. Toutefois, d’autres parties de WCF (telles que la méthode générique GetBody ou la sérialisation des erreurs SOAP) peuvent être limitées à utiliser uniquement les XmlObjectSerializer sous-classes (DataContractSerializer et NetDataContractSerializer, mais pas les XmlSerializer), ou peut même être codées en dur pour utiliser uniquement le DataContractSerializer.

Le XmlSerializer est le moteur de sérialisation utilisé dans les services Web ASP.NET. Le DataContractSerializer est le nouveau moteur de sérialisation qui prend en charge le nouveau modèle de programmation de contrat de données. DataContractSerializer est le choix par défaut, et le choix d’utiliser le XmlSerializer peut être effectué par opération à l’aide de l’attribut DataContractFormatAttribute .

DataContractSerializerOperationBehavior et XmlSerializerOperationBehavior sont les comportements d’opération responsables de l’intégration des formateurs de messages pour le DataContractSerializer et le XmlSerializer, respectivement. Le comportement DataContractSerializerOperationBehavior peut réellement fonctionner avec n'importe quel sérialiseur qui dérive de XmlObjectSerializer, y compris NetDataContractSerializer (décrit en détail dans Utilisation de la sérialisation autonome). Le comportement appelle l'une des surcharges de méthode virtuelle CreateSerializer pour obtenir le sérialiseur. Pour brancher un autre sérialiseur, créez une sous-classe DataContractSerializerOperationBehavior et substituez les deux surcharges CreateSerializer .

Voir aussi