Partager via


Utilisation de contrats de message

En général lors de la génération d'applications Windows Communication Foundation (WCF), les développeurs accordent une attention particulière aux structures de données et aux problèmes de sérialisation, et n'ont pas à se soucier des messages dans lesquels les données sont stockées. Pour ces applications, créer des contrats de données pour les paramètres ou les valeurs de retour est une procédure simple. (Pour plus d'informations, consultez Spécification du transfert de données dans des contrats de service.)

Toutefois, le contrôle complet sur la structure d'un message SOAP est parfois aussi important que celui sur son contenu. Cela se vérifie tout particulièrement lorsque l'interopérabilité est importante ou pour contrôler spécifiquement des problèmes de sécurité au niveau du message ou de la partie de message. Dans ce cas, vous pouvez créer un contrat de message vous permettant d'utiliser un type pour un paramètre ou une valeur de retour qui sérialise directement dans le message SOAP spécifique dont vous avez besoin.

Cette rubrique explique comment utiliser les divers attributs de contrat de message afin de créer un contrat de message spécifique pour votre opération.

Utilisation de contrats de message dans les opérations

WCF prend en charge des opérations modélisées sur le style d'appel de procédure distante (RPC, Remote Procedure Call) ou le style de messagerie. Dans une opération de style RPC, vous pouvez utiliser n'importe quel type sérialisable, et vous disposez des fonctionnalités disponibles aux appels locaux, tels que les paramètres multiples et les paramètres ref et out. Dans ce style, le formulaire de sérialisation choisi contrôle la structure des données des messages sous-jacents, mais l'exécution WCF crée les messages eux-mêmes afin de prendre en charge l'opération. Cela permet aux développeurs qui ne sont pas familiarisés avec SOAP et les messages SOAP de créer et d'utiliser rapidement et facilement des applications de service.

L'exemple de code suivant présente une opération de service modélisée sur le style RPC.

[OperationContract]
public BankingTransactionResponse PostBankingTransaction(BankingTransaction bt);

Habituellement, un contrat de données est suffisant pour définir le schéma des messages. Par exemple, cela est suffisant pour la plupart des applications dans l'exemple précédent si BankingTransaction et BankingTransactionResponse ont des contrats de données pour définir le contenu des messages SOAP sous-jacents. Pour plus d'informations sur les contrats de données, consultez Utilisation de contrats de données.

Toutefois, il peut parfois s'avérer nécessaire de contrôler de manière précise la façon dont un type est mappé à un message SOAP transmis sur le câble. Le scénario le plus courant dans ce cas consiste à insérer des en-têtes SOAP personnalisés. Un autre consiste à définir des propriétés de sécurité pour le corps et les en-têtes du message, c'est à-dire, à déterminer si ces éléments sont chiffrés et signés numériquement. Les opérations de style de messagerie fournissent ce contrôle.

Une opération de style de messagerie a au plus un paramètre et une valeur de retour où les deux types sont des types de messages ; autrement dit, ils sérialisent directement dans une structure de message SOAP spécifiée. Il peut s'agir de n'importe quel type marqué avec le type MessageContractAttribute ou Message. L'exemple de code suivant présente une opération similaire au style RCP précédent, mais qui utilise le style de messagerie.

Par exemple, si BankingTransaction et BankingTransactionResponse sont les deux types qui ont des contrats de message, alors le code dans les opérations suivantes est valide.

[OperationContract]
BankingTransactionResponse Process(BankingTransaction bt);
[OperationContract]
void Store(BankingTransaction bt);
[OperationContract]
BankingTransactionResponse GetResponse();

Toutefois, le code suivant n'est pas valide.

[OperationContract]
bool Validate(BankingTransaction bt);
//Invalid - the return type is not a message contract.
[OperationContract]
void Reconcile(BankingTransaction bt1, BankingTransaction bt2);
//Invalid – there is more than one parameter.

Une exception est levée pour toute opération qui implique un type de contrat de message et ne suit pas l'un des modèles valides. Bien évidemment, les opérations qui n'impliquent pas de types de contrats de message ne sont pas soumises à ces restrictions.

Si un type dispose à la fois d'un contrat de message et d'un contrat de données, seul son contrat de message est pris en compte lorsque le type est utilisé dans une opération.

Définition de contrats de message

Pour définir un contrat de message pour un type (autrement dit, définir le mappage entre le type et une enveloppe SOAP), appliquez MessageContractAttribute au type. Puis appliquez MessageHeaderAttribute aux membres du type que vous souhaitez créer dans les en-têtes SOAP et appliquez MessageBodyMemberAttribute aux membres que vous souhaitez créer dans les parties du corps SOAP du message.

Le code suivant fournit un exemple d'utilisation d'un contrat de message.

[MessageContract]
public class BankingTransaction
{
  [MessageHeader] public Operation operation;
  [MessageHeader] public DateTime transactionDate;
  [MessageBodyMember] private Account sourceAccount;
  [MessageBodyMember] private Account targetAccount;
  [MessageBodyMember] public int amount;

}

Lors de l'utilisation de ce type comme paramètre d'opération, une enveloppe SOAP est générée avec des en-têtes supplémentaires appelés operation et transactionDate, qui contiennent le contenu des champs operation et transactionDate. Le corps SOAP se compose d'un élément wrapper contenant des éléments appelés sourceAccount and targetAccount, du type de contrat de données Account et du amount du type entier.

Vous pouvez appliquer MessageHeaderAttribute et MessageBodyMemberAttribute à l'ensemble des champs, propriétés et événements, indépendamment du fait qu'ils soient publics, privés, protégés ou internes.

ms730255.note(fr-fr,VS.90).gifRemarque :
Les attributs KnownTypeAttribute sont ignorés dans les contrats de message. Si un KnownTypeAttribute est requis, placez-le sur l'opération qui utilise le contrat de message en question.

Utilisation de types personnalisés dans des contrats de message

Chaque partie de corps de message et en-tête de message individuel est sérialisé (converti en XML) à l'aide du moteur de sérialisation choisi du contrat de service dans lequel le message est utilisé. Le moteur de sérialisation par défaut, XmlFormatter, peut gérer n'importe quel type ayant un contrat de données, soit explicitement (en ayant System.Runtime.Serialization.DataContractAttribute) ou implicitement (en étant un type primitif, en ayant System.SerializableAttribute, etc.). Pour plus d'informations, consultez Utilisation de contrats de données.

Dans l'exemple précédent, les types Operation et BankingTransactionData doivent avoir un contrat de données, et transactionDate est sérialisable car DateTime est un type primitif (et a ainsi un contrat de données implicite).

Cependant, il est possible de basculer sur un autre moteur de sérialisation, XmlSerializer. Dans ce cas, vous devez vous assurer que tous les types utilisés pour les en-têtes de message et les parties du corps sont sérialisables à l'aide de XmlSerializer.

Utilisation de tableaux dans des contrats de message

Vous pouvez utiliser des tableaux d'éléments répétitifs dans des contrats de message de deux manières différentes.

La première méthode consiste à utiliser MessageHeaderAttribute ou MessageBodyMemberAttribute directement sur le tableau. Dans ce cas, l'ensemble du tableau est sérialisé comme un seul élément (c'est-à-dire, un seul en-tête ou un seul corps) avec plusieurs éléments enfants. Examinons la classe dans l'exemple suivant :

[MessageContract]
public class BankingDepositLog
{
  [MessageHeader] public int numRecords
  [MessageHeader] public DepositRecord records[];
  [MessageHeader] public int branchID;
}

Les en-têtes SOAP sont similaires aux éléments suivants.

<BankingDepositLog>
<numRecords>3</numRecords>
<records>
  <DepositRecord>Record1</DepositRecord>
  <DepositRecord>Record2</DepositRecord>
  <DepositRecord>Record3</DepositRecord>
</records>
<branchID>20643</branchID>
</BankingDepositLog>

L'autre méthode consiste à utiliser MessageHeaderArrayAttribute. Dans ce cas, chaque élément de tableau est sérialisé indépendamment et de manière à n'avoir qu'un seul en-tête comme suit.

<numRecords>3</numRecords>
<records>Record1</records>
<records>Record2</records>
<records>Record3</records>
<branchID>20643</branchID>

Le nom par défaut des entrées de tableau correspond à celui du membre auquel l'attribut MessageHeaderArrayAttribute est appliqué.

L'attribut MessageHeaderArrayAttribute hérite de MessageHeaderAttribute. Il a le même jeu de fonctionnalités que les attributs non-tableau ; il est possible, par exemple, de définir l'ordre, le nom et l'espace de noms d'un tableau d'en-têtes de la même façon que pour un en-tête unique. Lorsque vous utilisez la propriété Order sur un tableau, elle s'applique à l'ensemble de celui-ci.

Vous ne pouvez appliquer MessageHeaderArrayAttribute qu'aux tableaux, et non pas aux collections.

Utilisation de tableaux d'octets dans des contrats de message

Les tableaux d'octets, lorsqu'ils sont utilisés avec des attributs non-tableau (MessageBodyMemberAttribute et MessageHeaderAttribute), ne sont pas traités comme des tableaux mais comme un type primitif spécial représenté sous forme de données codées en base 64 dans le XML résultant.

Lorsque vous utilisez des tableaux d'octets avec l'attribut de tableau MessageHeaderArrayAttribute, les résultats varient en fonction du sérialiseur utilisé. Avec le sérialiseur par défaut, le tableau est représenté sous forme d'une entrée individuelle pour chaque octet. Toutefois, lorsque XmlSerializer est sélectionné, (à l'aide de XmlSerializerFormatAttribute sur le contrat de service) les tableaux d'octets sont traités comme des données Base64es indépendamment de l'utilisation d'attributs de tableau ou non-tableau.

Signature et chiffrement des parties du message

Un contrat de message peut indiquer si les en-têtes et/ou le corps du message doivent être signés numériquement et chiffrés.

Cela s'effectue en définissant la propriété System.ServiceModel.MessageContractMemberAttribute.ProtectionLevel sur les attributs MessageHeaderAttribute et MessageBodyMemberAttribute. La propriété est une énumération du type System.Net.Security.ProtectionLevel et peut être définie à None (pas de chiffrement ni de signature), Sign (signature numérique uniquement) ou EncryptAndSign (chiffrement et signature numérique). La valeur par défaut est None.

Pour que ces fonctionnalités de sécurité fonctionnent, vous devez configurer la liaison et les comportements correctement. Si vous utilisez ces fonctionnalités de sécurité sans configuration correcte (par exemple, essayer de signer un message sans fournir vos informations d'identification), une exception est levée lors de la validation.

Pour les en-têtes de message, le niveau de protection est déterminé individuellement pour chaque en-tête.

Pour les parties de corps de message, le niveau de protection peut être considéré comme le « niveau de protection minimal ». Le corps a un seul niveau de protection, quel que soit le nombre de parties du corps. Le niveau de protection du corps est déterminé par le paramètre de propriété ProtectionLevel de toutes les parties du corps. Toutefois, vous devez définir le niveau de protection de chaque partie du corps au niveau de protection minimal réel requis.

Examinons la classe dans l'exemple de code suivant :

[MessageContract]
public class PatientRecord
{
   [MessageHeader(ProtectionLevel=None)] public int recordID;
   [MessageHeader(ProtectionLevel=Sign)] public string patientName;
   [MessageHeader(ProtectionLevel=EncryptAndSign)] public string SSN;
   [MessageBodyMember(ProtectionLevel=None)] public string comments;
   [MessageBodyMember(ProtectionLevel=Sign)] public string diagnosis;
   [MessageBodyMember(ProtectionLevel=EncryptAndSign)] public string medicalHistory;
}

Dans cet exemple, l'en-tête recordID n'est pas protégé, patientName est signed, et SSN est chiffré et signé. EncryptAndSign étant appliqué sur au moins une partie du corps (medicalHistory), l'ensemble du message est donc chiffré et signé, même si les commentaires et les parties de corps de diagnostic spécifient des niveaux de protection inférieurs.

Contrôle des espaces de noms et noms des en-têtes et parties du corps

Dans la représentation SOAP d'un contrat de message, chaque en-tête et le partie du corps mappe à un élément XML ayant un nom et un espace de noms.

Par défaut, l'espace de noms est le même que celui du contrat de service auquel le message participe, et le nom est déterminé par le nom de membre auquel les attributs MessageHeaderAttribute ou MessageBodyMemberAttribute sont appliqués.

Vous pouvez modifier ces valeurs par défaut en manipulant System.ServiceModel.MessageContractMemberAttribute.Name et System.ServiceModel.MessageContractMemberAttribute.Namespace****(sur la classe parente des attributs MessageHeaderAttribute et MessageBodyMemberAttribute).

Examinons la classe dans l'exemple de code suivant :

[MessageContract]
public class BankingTransaction
{
  [MessageHeader] public Operation operation;
  [MessageHeader(Namespace="http://schemas.contoso.com/auditing/2005")] public bool IsAudited;
  [MessageBodyMember(Name="transactionData")] public BankingTransactionData theData;
}

Dans cet exemple, l'en-tête IsAudited est dans l'espace de noms spécifié dans le code, et le corps qui représente le membre theData est représenté par un élément XML avec le nom transactionData.

Contrôle de l'encapsulation des parties de corps SOAP

Par défaut, les parties de corps SOAP sont sérialisées à l'intérieur d'un élément encapsulé. Par exemple, le code suivant affiche l'élément wrapper HelloGreetingMessage généré à partir du nom du type MessageContractAttribute dans le contrat de message du message HelloGreetingMessage.

Pour supprimer l'élément wrapper, affectez false à la propriété IsWrapped. Pour contrôler le nom et l'espace de noms de l'élément wrapper, utilisez les propriétés WrapperName et WrapperNamespace.

ms730255.note(fr-fr,VS.90).gifRemarque :
La présence de plusieurs parties de corps de messages non encapsulées n'est pas conforme à WS-I Basic Profile 1.1 et n'est pas recommandée lors de la conception de nouveaux contrats de message. Toutefois, il peut s'avérer nécessaire d'avoir plusieurs parties de corps du message non encapsulées dans certains scénarios d'interopérabilité spécifiques. Si vous prévoyez de transmettre plusieurs éléments de données dans un corps de message, il est recommandé d'utiliser le mode (encapsulé) par défaut. La présence de plusieurs en-têtes de message dans des messages non encapsulés est complètement acceptable.

Action SOAP

SOAP et les standards de services Web associés définissent une propriété appelée Action qui peut être présente pour chaque message SOAP envoyé. Les propriétés System.ServiceModel.OperationContractAttribute.Action et System.ServiceModel.OperationContractAttribute.ReplyAction de l'opération contrôlent la valeur de cette propriété.

Attributs d'en-tête SOAP

Le standard SOAP définit les attributs suivants qui peuvent exister dans un en-tête :

  • Actor/Role (Actor dans SOAP 1.1, Role dans SOAP 1.2)
  • MustUnderstand
  • Relay

L'attribut Actor ou Role spécifie l'URI (Uniform Resource Identifier) du nœud auquel un en-tête donné est destiné. L'attribut MustUnderstand spécifie si le nœud qui traite cet en-tête doit le comprendre. L'attribut Relay spécifie si l'en-tête sera relayé aux nœuds aval. WCF n'exécute pas de traitement de ces attributs sur les messages entrants, à l'exception de l'attribut MustUnderstand, tel que spécifié dans la section « Contrôle de version des contrats de message » développée ultérieurement dans cette rubrique. Toutefois, il vous permet de lire et d'écrire ces attributs si nécessaire, comme dans la description suivante.

Lors de l'envoi d'un message, ces attributs ne sont pas émis par défaut. Vous pouvez modifier ce paramètre de deux façons : La première méthode consiste à affecter statiquement aux attributs les valeurs souhaitée en modifiant les propriétés System.ServiceModel.MessageHeaderAttribute.Actor, System.ServiceModel.MessageHeaderAttribute.MustUnderstand, et System.ServiceModel.MessageHeaderAttribute.Relay, tel qu'indiqué dans l'exemple de code suivant. (Notez qu'il n'y a pas de propriété Role ; la définition de la propriété Actor entraîne l'émission de Role si vous utilisez SOAP 1.2).

[MessageContract]
public class BankingTransaction
{
  [MessageHeader(Actor="http://auditingservice.contoso.com", MustUnderstand=true)] public bool IsAudited;
  [MessageHeader] public Operation operation;
..[MessageBodyMember] public BankingTransactionData theData;
}

La deuxième méthode permettant de contrôler ces attributs s'appuie sur un mécanisme dynamique, via le code. Pour ce faire, encapsulez le type d'en-tête souhaité dans le type MessageHeader (assurez-vous de ne pas confondre ce type avec la version non générique), puis utilisez le type avec MessageHeaderAttribute. Puis, utilisez des propriétés sur MessageHeader pour définir les attributs SOAP, tel qu'indiqué dans l'exemple de code suivant.

[MessageContract]
public class BankingTransaction
{
  [MessageHeader] public MessageHeader<bool> IsAudited;
  [MessageHeader] public Operation operation;
..[MessageBodyMember] public BankingTransactionData theData;
}
//… application code:
BankingTransaction bt = new BankingTransaction();
bt.IsAudited = new MessageHeader<bool>();
bt.IsAudited.Content = false; //Set IsAudited header value to "false"
bt.IsAudited.Actor="http://auditingservice.contoso.com";
bt.IsAudited.MustUnderstand=true;

Si vous utilisez à la fois les mécanismes de contrôle dynamique et statique, les paramètres statiques sont utilisés comme valeur par défaut mais peuvent être substitués ultérieurement en utilisant le mécanisme dynamique, tel qu'indiqué dans le code suivant.

[C#]
[MessageHeader(MustUnderstand=true)] public MessageHeader<Person> documentApprover;
//… later on in the code:
BankingTransaction bt = new BankingTransaction();
bt.documentApprover= new MessageHeader<Person>();
bt.documentApprover.MustUnderstand=false; //override the static default of 'true'

La création d'en-têtes répétés avec contrôle d'attribut dynamique est autorisée, tel qu'indiqué dans le code suivant.

[MessageHeaderArray] public MessageHeader<Person> documentApprovers[];

Côté réception, la lecture de ces attributs SOAP peut être faite uniquement si la classe MessageHeader est utilisée pour l'en-tête dans le type. Examinez les propriétés Actor, Relay ou MustUnderstand d'un en-tête du type MessageHeader pour découvrir les paramètres d'attribut sur le message reçu.

Lorsqu'un message est reçu puis renvoyé, les paramètres d'attribut SOAP font l'aller-retour uniquement pour les en-têtes du type MessageHeader.

Ordre des parties de corps SOAP

Dans certains cas, il peut s'avérer nécessaire de contrôler l'ordre des parties du corps. L'ordre des éléments du corps est alphabétique par défaut, mais peut être contrôlé par la propriété System.ServiceModel.MessageBodyMemberAttribute.Order. Cette propriété a la même sémantique que la propriété System.Runtime.Serialization.DataMemberAttribute.Order, à l'exception du comportement dans les scénarios d'héritage (dans les contrats de message, les membres du corps du type de base ne sont pas triés avant les membres du corps du type dérivé). Pour plus d'informations, consultez Classement des membres de données.

Dans l'exemple suivant, amount viendrait normalement en premier car c'est le premier dans l'ordre alphabétique. Cependant, la propriété Order le met en troisième position.

[MessageContract]
public class BankingTransaction
{
  [MessageHeader] public Operation operation;
  [MessageBodyMember(Order=1)] public Account sourceAccount;
  [MessageBodyMember(Order=2)] public Account targetAccount;
  [MessageBodyMember(Order=3)] public int amount;
}

Contrôle de version des contrats de message

Il peut parfois s'avérer nécessaire de modifier des contrats de message. Par exemple, une nouvelle version de votre application peut ajouter un en-tête supplémentaire à un message. Ensuite, lors de l'envoi de la nouvelle version à l'ancienne, le système doit gérer en-tête supplémentaire, ainsi qu'un en-tête manquant lorsqu'il va dans l'autre direction.

Les règles suivantes s'appliquent pour le contrôle de version des en-têtes :

  • WCF ne proteste pas contre l'en-tête manquant ; les membres correspondants restent à leurs valeurs par défaut.
  • WCF ignore également les en-têtes supplémentaires inattendus. La seule exception à cette règle est si l'en-tête supplémentaire a un attribut MustUnderstand défini à true dans le message SOAP entrant ; dans ce cas, une exception est levée car un en-tête qui doit être compris ne peut pas être traité.

Les corps de message ont des règles de contrôle de version similaires ; les parties de corps de message manquantes et supplémentaires sont ignorées.

Considérations sur l'héritage

Un type de contrat de message peut hériter d'un autre type, tant que le type de base a également un contrat de message.

Lors de la création ou de l'accès à un message à l'aide d'un type de contrat de message qui hérite d'autres types de contrats de message, les règles suivantes s'appliquent :

  • Tous les en-têtes de message dans la hiérarchie d'héritage sont regroupés pour former le jeu complet d'en-têtes pour le message.
  • Toutes les parties de corps du message dans la hiérarchie d'héritage sont regroupées pour former le corps du message complet. Les parties du corps sont ordonnées conformément aux règles de classement habituelles (par la propriété System.ServiceModel.MessageBodyMemberAttribute.Order, puis par ordre alphabétique), sans tenir en compte de leur place dans la hiérarchie d'héritage. L'utilisation d'un héritage de contrat de message dans lequel les parties de corps du message se produisent à plusieurs niveaux de l'arborescence d'héritage est fortement déconseillée. Si une classe de base et une classe dérivée définissent un en-tête ou une partie du corps avec le même nom, le membre de la classe la plus de base est utilisé pour stocker la valeur de cet en-tête ou partie du corps.

Examinons les classes dans l'exemple de code suivant.

[MessageContract]
public class PersonRecord
{
  [MessageHeader(Name="ID")] public int personID;
  [MessageBodyMember] public string patientName;
}

[MessageContract]
public class PatientRecord : PersonRecord
{
  [MessageHeader(Name="ID")] public int patientID;
  [MessageBodyMember] public string diagnosis;
}

La classe PatientRecord décrit un message avec un en-tête appelé ID. L'en-tête correspond à personID et non pas au membre patientID, car le membre le plus de base est choisi. Par conséquent, le champ patientID est inutile dans ce cas. Le corps du message contient l'élément diagnosis suivi par l'élément patientName, car il s'agit de l'ordre alphabétique. Notez que l'exemple présente un modèle qui est fortement déconseillé : les contrats de message de base et dérivés ont tous deux des parties de corps de message.

Considérations sur WSDL

Lors de la génération d'un contrat WSDL (Web Services Description Language) à partir d'un service qui utilise des contrats de message, il est important de se souvenir que les fonctionnalités de contrat de message ne sont pas toutes répercutées dans le WSDL résultant. Examinons les points suivants :

  • WSDL ne peut pas exprimer le concept d'un tableau d'en-têtes. Lors de la création de messages avec un tableau d'en-têtes à l'aide de MessageHeaderArrayAttribute, le WSDL résultant ne reflète qu'un seul en-tête au lieu du tableau.
  • Le document WSDL résultant ne reflète pas certaines informations de niveau de protection.
  • Le type de message généré dans le WSDL a le même nom que le nom de classe du type de contrat de message.
  • Lors de l'utilisation du même contrat de message dans plusieurs opérations, plusieurs types de message sont générés dans le document WSDL. Les noms sont rendus uniques en ajoutant les nombres « 2 », « 3 », et ainsi de suite, pour les utilisations suivantes. Lors de la réimportation du WSDL, plusieurs types de contrats de message sont créés et sont identiques à l'exception de leurs noms.

Considérations sur le codage SOAP

WCF vous permet d'utiliser le style de codage SOAP hérité de XML, bien que cela ne soit pas recommandé. Lors de l'utilisation de ce style (en affectant à la propriété Use la valeur Encoded sur le System.ServiceModel.XmlSerializerFormatAttribute appliqué au contrat de service), les considérations supplémentaires suivantes s'appliquent :

  • Les en-têtes de message ne sont pas pris en charge ; cela signifie que l'attribut MessageHeaderAttribute et l'attribut de tableau MessageHeaderArrayAttribute sont incompatibles avec le codage SOAP.

  • Si le contrat de message n'est pas encapsulé, c'est-à-dire, si la propriété IsWrapped a la valeur false, le contrat de message ne peut avoir qu'une seule partie de corps.

  • Le nom de l'élément wrapper du contrat de message de demande doit correspondre au nom d'opération. Utilisez la propriété WrapperName du contrat de message à cette fin.

  • Le nom de l'élément wrapper du contrat de message de réponse doit être le même que celui de l'opération suffixée par 'Response'. Utilisez la propriété WrapperName du contrat de message à cette fin.

  • Le codage SOAP conserve les références d'objet. Examinons par exemple le code suivant.

    [MessageContract(WrapperName="updateChangeRecord")]
    public class ChangeRecordRequest
    {
      [MessageBodyMember] Person changedBy;
      [MessageBodyMember] Person changedFrom;
      [MessageBodyMember] Person changedTo;
    }
    
    [MessageContract(WrapperName="updateChangeRecordResponse")]
    public class ChangeRecordResponse
    {
      [MessageBodyMember] Person changedBy;
      [MessageBodyMember] Person changedFrom;
      [MessageBodyMember] Person changedTo;
    }
    
    //… application code:
    ChangeRecordRequest cr = new ChangeRecordRequest();
    Person p = new Person("John Doe");
    cr.changedBy=p;
    cr.changedFrom=p;
    cr.changedTo=p;
    

Après avoir sérialisé le message à l'aide du codage SOAP, changedFrom et changedTo ne contiennent pas leurs propres copies de p, mais pointent à la place sur la copie à l'intérieur de l'élément changedBy.

Considérations sur les performances

Chaque partie de corps du message et en-tête de message est sérialisé indépendamment des autres. Par conséquent, des espaces de noms identiques peuvent être à nouveau déclarés pour chaque en-tête et partie du corps. Pour améliorer les performances, tout particulièrement en termes de taille du message sur le câble, consolidez plusieurs en-têtes et parties de corps dans un en-tête ou une partie de corps unique. Par exemple, à la place du code suivant :

[MessageContract]
public class BankingTransaction
{
  [MessageHeader] public Operation operation;
  [MessageBodyMember] public Account sourceAccount;
  [MessageBodyMember] public Account targetAccount;
  [MessageBodyMember] public int amount;
}

Utilisez celui-ci.

[MessageContract]
public class BankingTransaction
{
  [MessageHeader] public Operation operation;
  [MessageBodyMember] public OperationDetails details;
}

[DataContract]
public class OperationDetails
{
  [DataMember] public Account sourceAccount;
  [DataMember] public Account targetAccount;
  [DataMember] public int amount;
}

Contrats de message et modèle asynchrone basé sur les événements

Les règles de conception pour le modèle asynchrone basé sur les événements stipulent que si plusieurs valeurs sont retournées, une valeur est retournée comme la propriété Result et les autres sont retournées comme les propriétés sur l'objet EventArgs. Il en découle que si un client importe des métadonnées à l'aide des options de commande asynchrone basées sur les événements et que l'opération retourne plusieurs valeurs, l'objet EventArgs par défaut retourne une valeur comme la propriété Result et le reste sont des propriétés de l'objet EventArgs.

Pour recevoir l'objet message comme la propriété Result et que les valeurs retournées sur cet objet soient des propriétés, utilisez l'option de commande /messageContract. Cette opération génère une signature qui retourne le message de réponse comme la propriété Result sur l'objet EventArgs. Toutes les valeurs de retour internes sont ensuite des propriétés de l'objet de message de réponse.

Voir aussi

Concepts

Utilisation de contrats de données
Conception et implémentation de services