Verwendung von Nachrichtenverträgen

Üblicherweise achten Entwickler*innen bei der Erstellung von WCF-Anwendungen (Windows Communication Foundation) streng auf die Datenstrukturen und Serialisierungsprobleme und müssen nicht auf die Struktur der Nachrichten achten, in denen die Daten transportiert werden. Für diese Anwendungen ist die Erstellung von Datenverträgen für die Parameter oder Rückgabewerte ein einfacher Vorgang. (Weitere Informationen finden Sie unter Festlegen der Datenübertragung in Dienstverträgen.)

Allerdings ist zuweilen die vollständige Kontrolle über die Struktur einer SOAP-Nachricht wichtiger als die Kontrolle über dessen Inhalte. Dies gilt insbesondere, wenn Interoperabilität wichtig ist oder um Sicherheitsprobleme speziell auf der Ebene der Nachricht oder des Nachrichtenteils zu kontrollieren. In diesen Fällen können Sie einen Nachrichtenvertrag erstellen, der es Ihnen ermöglicht, die Struktur der benötigten SOAP-Nachricht anzugeben.

Dieses Thema beschreibt, wie die unterschiedlichen Attribute für Nachrichtenverträge zur Erstellung eines spezifischen Nachrichtenvertrags für Ihren Vorgang verwendet werden.

Verwenden von Nachrichtenverträgen in Vorgängen

WCF unterstützt Vorgänge, die entweder als Remoteprozeduraufruf (RPC, Remote Procedure Call) oder im Messagingstil erstellt werden. Bei einem Vorgang im RPC-Stil können Sie jeden serialisierbaren Typ verwenden, und Sie haben Zugriff auf die Funktionen, die für lokale Aufrufe verfügbar sind, beispielsweise mehrere Parameter sowie der ref-Parameter und der out-Parameter. In diesem Stil steuert die gewählte Art der Serialisierung die Struktur der Daten in den zugrunde liegenden Nachrichten, und die WCF-Runtime erstellt die Nachrichten, um den Vorgang zu unterstützen. Dadurch können Entwickler, die sich mit SOAP und SOAP-Nachrichten nicht auskennen, Dienstanwendungen schnell und einfach erstellen und verwenden.

Das folgende Codebeispiel zeigt einen im RPC-Stil erstellten Dienstvorgang.

[OperationContract]  
public BankingTransactionResponse PostBankingTransaction(BankingTransaction bt);  

Normalerweise ist ein Datenvertrag ausreichend, um das Schema für die Nachrichten zu definieren. Beispielsweise reicht es im vorherigen Beispiel für die meisten Anwendungen aus, wenn BankingTransaction und BankingTransactionResponse über Datenverträge für die Inhaltsdefinition der zugrunde liegenden SOAP-Nachrichten verfügen. Weitere Informationen zu Datenverträgen finden Sie unter Verwenden von Datenverträgen.

Zuweilen ist es allerdings notwendig, die Struktur der übertragenen SOAP-Nachricht genau zu steuern. Das gängigste Szenario hierfür besteht aus dem Einfügen von benutzerdefinierten SOAP-Headern. Ein weiteres übliches Szenario ist die Definition von Sicherheitseigenschaften für Nachrichtenheader und -text, das heißt, die Entscheidung, ob diese Elemente digital signiert und verschlüsselt werden. Schließlich erfordern eine Reihe von Drittanbieter-SOAP-Stapeln Nachrichten in einem bestimmten Format. Vorgänge im Messagingstil bieten diese Kontrolle.

Ein Vorgang im Messagingstil verfügt über höchstens einen Parameter und einen Rückgabewert. Bei beiden Typen handelt es sich um Nachrichtentypen, d. h. sie serialisieren direkt in eine festgelegte SOAP-Nachrichtenstruktur. Hierbei kann es sich um jeden Typ mit der Kennzeichnung MessageContractAttribute oder um den Message-Typ handeln. Das folgende Codebeispiel zeigt einen Vorgang, der dem vorangehenden Vorgang im RCP-Stil ähnelt, aber den Messagingstil nutzt.

Wenn sowohl BankingTransaction als auch BankingTransactionResponse Typen sind, bei denen es sich um Nachrichtenverträge handelt, ist der Code in den folgenden Vorgängen gültig.

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

Der folgende Code ist allerdings ungültig.

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

Für jeden Vorgang, der einen Nachrichtenvertragstyp beinhaltet und nicht eines der gültigen Muster einhält, wird eine Ausnahme ausgelöst. Natürlich unterliegen Vorgänge, die keine Nachrichtenvertragstypen einschließen, diesen Einschränkungen nicht.

Verfügt ein Typ sowohl über einen Nachrichtenvertrag als auch über einen Datenvertrag, wird nur der Nachrichtenvertrag berücksichtigt, wenn der Typ in einem Vorgang zum Einsatz kommt.

Definition von Nachrichtenverträgen

Zur Definition eines Nachrichtenvertrags für einen Typ (d. h. zur Definition der Zuordnung zwischen Typ und SOAP-Umschlag) wenden Sie MessageContractAttribute auf den Typ an. Wenden Sie dann das MessageHeaderAttribute auf die Member an, die Sie SOAP-Headern machen möchten, und wenden Sie dann das MessageBodyMemberAttribute auf die Member an,die Sie zu Teilen des SOAP-Texts der Nachricht machen möchten.

Der folgende Code stellt ein Beispiel für die Verwendung eines Nachrichtenvertrags dar.

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

Bei der Verwendung dieses Typs als Vorgangsparameter wird der folgende SOAP-Umschlag generiert:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">  
  <s:Header>  
    <h:operation xmlns:h="http://tempuri.org/" xmlns="http://tempuri.org/">Deposit</h:operation>  
    <h:transactionDate xmlns:h="http://tempuri.org/" xmlns="http://tempuri.org/">2012-02-16T16:10:00</h:transactionDate>  
  </s:Header>  
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">  
    <BankingTransaction xmlns="http://tempuri.org/">  
      <amount>0</amount>  
      <sourceAccount xsi:nil="true"/>  
      <targetAccount xsi:nil="true"/>  
    </BankingTransaction>  
  </s:Body>  
</s:Envelope>  

Beachten Sie, dass operation und transactionDate als SOAP-Kopfzeilen und der SOAP-Textkörper aus einem Wrapperelement BankingTransaction bestehen, sourceAccountdas , targetAccountund amount.

Sie können MessageHeaderAttribute und MessageBodyMemberAttribute auf alle Felder, Eigenschaften und Ereignisse anwenden, unabhängig davon, ob diese öffentlich, privat, geschützt oder intern sind.

Das MessageContractAttribute ermöglicht Ihnen das Angeben des WrapperName-Attributs und des WrapperNamespace-Attributs, die den Namen des Wrapperelements im Textkörper der SOAP-Nachricht steuern. Standardmäßig wird der Name des Typs des Nachrichtenvertrags für den Wrapper verwendet, und der Namespace, in dem der Nachrichtenvertrag http://tempuri.org/ definiert ist, fungiert als Standardnamespace.

Hinweis

KnownTypeAttribute-Attribute werden in Nachrichtenverträgen ignoriert. Ist ein KnownTypeAttribute erforderlich, platzieren Sie es auf dem Vorgang, der den in Frage kommenden Nachrichtenvertrag nutzt.

Kontrollieren von Header- und Textteilnamen und Namespaces

In der SOAP-Darstellung eines Nachrichtenvertrags wird jeder Header und jeder Textteil einem XML-Element zugeordnet, das über einen Namen und einen Namespace verfügt.

Standardmäßig entspricht der Namespace dem Namespace des Dienstvertrags, an dem die Nachricht teilnimmt, und der Name wird durch den Membernamen festgelegt, auf den die Attribute MessageHeaderAttribute oder MessageBodyMemberAttribute angewandt wurden.

Die Standardeinstellungen können Sie ändern, indem Sie MessageContractMemberAttribute.Name und MessageContractMemberAttribute.Namespace bearbeiten (in der übergeordneten Klasse der Attribute MessageHeaderAttribute und MessageBodyMemberAttribute).

Betrachten Sie die Klasse im folgenden Codebeispiel.

[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;  
}  

In diesem Beispiel befindet sich der IsAudited-Header im Namespace, der vom Code festgelegt wird, und der Textteil, der den theData-Member darstellt, wird von einem XML-Element mit dem Namen transactionData abgebildet. Im Folgenden wird der XML-Code angezeigt, der für diesen Nachrichtenvertrag generiert wird.

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">  
  <s:Header>  
    <h:IsAudited xmlns:h="http://schemas.contoso.com/auditing/2005" xmlns="http://schemas.contoso.com/auditing/2005">false</h:IsAudited>  
    <h:operation xmlns:h="http://tempuri.org/" xmlns="http://tempuri.org/">Deposit</h:operation>  
  </s:Header>  
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">  
    <AuditedBankingTransaction xmlns="http://tempuri.org/">  
      <transactionData/>  
    </AuditedBankingTransaction>  
  </s:Body>  
</s:Envelope>  

Kontrolle, ob die SOAP-Textteile umbrochen werden

Standardmäßig sind die SOAP-Textteile in einem umbrochenen Element serialisiert. Beispielsweise zeigt der folgende Code das HelloGreetingMessage-Wrapperelement, das aus dem Namen des MessageContractAttribute-Typs im Nachrichtenvertrag für die HelloGreetingMessage-Nachricht generiert wird.

[MessageContract]
public class HelloGreetingMessage
{
  private string localGreeting;

  [MessageBodyMember(
    Name = "Salutations",
    Namespace = "http://www.examples.com"
  )]
  public string Greeting
  {
    get { return localGreeting; }
    set { localGreeting = value; }
  }
}

/*
 The following is the request message, edited for clarity.

  <s:Envelope>
    <s:Header>
      <!-- Note: Some header content has been removed for clarity.
      <a:Action>http://GreetingMessage/Action</a:Action>
      <a:To s:mustUnderstand="1"></a:To>
    </s:Header>
    <s:Body u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
      <HelloGreetingMessage xmlns="Microsoft.WCF.Documentation">
        <Salutations xmlns="http://www.examples.com">Hello.</Salutations>
      </HelloGreetingMessage>
    </s:Body>
 </s:Envelope>
 */
<MessageContract> _
Public Class HelloGreetingMessage
    Private localGreeting As String

    <MessageBodyMember(Name:="Salutations", Namespace:="http://www.examples.com")> _
    Public Property Greeting() As String
        Get
            Return localGreeting
        End Get
        Set(ByVal value As String)
            localGreeting = value
        End Set
    End Property
End Class

'  
'   The following is the request message, edited for clarity.
'    
'    <s:Envelope>
'      <s:Header>
'        <!-- Note: Some header content has been removed for clarity.
'        <a:Action>http://GreetingMessage/Action</a:Action> 
'        <a:To s:mustUnderstand="1"></a:To>
'      </s:Header>
'      <s:Body u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
'        <HelloGreetingMessage xmlns="Microsoft.WCF.Documentation">
'          <Salutations xmlns="http://www.examples.com">Hello.</Salutations>
'      </s:Body>
'   </s:Envelope>
'   

Um das Wrapperelement zu unterdrücken, legen Sie die IsWrapped-Eigenschaft auf false fest. Um den Namen und den Namespace des Wrapperelements zu kontrollieren, verwenden Sie die Eigenschaften WrapperName und WrapperNamespace.

Hinweis

Das Vorhandensein von mehr als einem Nachrichtentextteil in nicht umbrochenen Nachrichten ist nicht mit dem WS-I Basic Profile 1.1 kompatibel und wird bei der Gestaltung neuer Nachrichtenverträge nicht empfohlen. Allerdings kann es bei spezifischen Interoperabilitätsszenarien notwendig sein, mehr als einen nicht umbrochenen Nachrichtentextteil zu haben. Wenn Sie mehrere Daten in einem Nachrichtentext übertragen möchten, wird die Verwendung des Standardumbruchmodus empfohlen. Mehr als einen Nachrichtenheader in nicht umbrochenen Nachrichten zu haben, ist vollkommen akzeptabel.

Verwendung von benutzerdefinierten Typen innerhalb von Nachrichtenverträgen

Jeder einzelne Nachrichtenheader und Nachrichtentextteil wird mithilfe der ausgewählten Serialisierungs-Engine für den Dienstvertrag, bei dem die Nachricht verwendet wird, serialisiert (in XML konvertiert). Die Standardserialisierungs-Engine, XmlFormatter, kann jeden Typ verarbeiten, der über einen Datenvertrag verfügt; entweder explizit (durch System.Runtime.Serialization.DataContractAttribute) oder implizit (bei einem primitiven Typ mit System.SerializableAttribute usw.). Weitere Informationen finden Sie unter Verwenden von Datenverträgen.

Im vorangehenden Beispiel müssen die Typen Operation und BankingTransactionData über einen Datenvertrag verfügen, und transactionDate ist serialisierbar, da DateTime primitiv ist (und über einen impliziten Datenvertrag verfügt).

Es ist jedoch möglich, zu einer anderen Serialisierungs-Engine, dem XmlSerializer, umzuschalten. Wenn Sie umschalten, sollten Sie sicherstellen, dass alle für die Nachrichtenheader und Textteile verwendeten Typen mithilfe des XmlSerializer serialisierbar sind.

Verwendung von Arrays innerhalb von Nachrichtenverträgen

Sie können Arrays von sich wiederholenden Elementen in Nachrichtenverträgen auf zwei Arten verwenden.

Auf der einen Seite können Sie ein MessageHeaderAttribute oder ein MessageBodyMemberAttribute direkt auf dem Array anwenden. In diesem Fall wird das gesamte Array als ein Element (d. h. ein Header und ein Textteil) mit mehreren Unterelementen serialisiert. Betrachten Sie die Klasse im folgenden Beispiel.

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

Das Ergebnis sind SOAP-Header, die den folgenden Beispielen ähneln.

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

Eine Alternative besteht in der Verwendung des MessageHeaderArrayAttribute. In diesem Fall wird jedes Arrayelement unabhängig serialisiert, wobei jedes Arrayelement über einen Header verfügt (ähnlich wie im folgenden Beispiel).

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

Der Standardname für Arrayeinträge ist der Name des Members, auf den die MessageHeaderArrayAttribute-Attribute angewandt werden.

Das MessageHeaderArrayAttribute-Attribut erbt von MessageHeaderAttribute. Es verfügt über denselben Funktionssatz wie die Nicht-Arrayattribute. Beispielsweise ist es möglich, die Reihenfolge, den Namen und den Namespace für ein Array von Headern so einzurichten wie für einen Einzelheader. Wenn Sie die Order-Eigenschaft auf ein Array verwenden, gilt dies für das gesamte Array.

Sie können MessageHeaderArrayAttribute nur auf Arrays anwenden, nicht auf Sammlungen.

Verwendung von Bytearrays in Nachrichtenverträgen

Bytearrays werden bei der Verwendung mit Nicht-Arrayattributen (MessageBodyMemberAttribute und MessageHeaderAttribute) nicht als Arrays behandelt, sondern als spezielle primitive Typen, die als Base64-codierte Daten in der resultierenden XML dargestellt werden.

Wenn Sie Bytearrays mit dem Arrayattribut MessageHeaderArrayAttribute verwenden, hängen die Ergebnisse vom verwendeten Serialisierungsprogramm ab. Mit dem Standardserialisierungsprogramm wird das Array als einzelner Eintrag für jedes Byte dargestellt. Ist allerdings XmlSerializer ausgewählt (mithilfe von XmlSerializerFormatAttribute auf dem Dienstvertrag), werden Bytearrays als Base64-Daten behandelt, unabhängig davon, ob Array- oder Nicht-Arrayattribute verwendet werden.

Signieren und Verschlüsseln von Teilen der Nachricht

Ein Nachrichtenvertrag kann angeben, ob die Header und/oder der Text der Nachricht digital signiert und verschlüsselt werden soll.

Dies wird erreicht, indem die MessageContractMemberAttribute.ProtectionLevel-Eigenschaften in den Attributen MessageHeaderAttribute und MessageBodyMemberAttribute festgelegt wird. Die Eigenschaft ist eine Enumeration des System.Net.Security.ProtectionLevel-Typs und kann auf None (keine Verschlüsselung oder Signatur), Sign (nur digitale Signatur) oder EncryptAndSign (sowohl Verschlüsselung als auch digitale Signatur) festgelegt werden. Der Standardwert lautet EncryptAndSign.

Damit diese Sicherheitsfunktionen arbeiten, müssen Sie die Bindung und das Verhalten ordnungsgemäß konfigurieren. Wenn Sie diese Sicherheitsfunktionen ohne angemessene Konfiguration verwenden (beispielsweise der Versuch der Signierung einer Nachricht ohne Bereitstellung Ihrer Anmeldeinformationen), wird zum Validierungszeitpunkt eine Ausnahme ausgelöst.

Bei Nachrichtenheadern wird die Schutzebene für jeden Header einzeln festgelegt.

Für Nachrichtentextteile kann die Schutzebene als „minimale Schutzebene“ angesehen werden. Der Text verfügt jedoch unabhängig von der Anzahl der Textteile nur über eine Schutzebene. Die Schutzebene des Texts wird durch die höchste ProtectionLevel-Eigenschaftseinstellung aller Textteile bestimmt. Allerdings sollten Sie die Schutzebene eines jeden Textteils auf die tatsächlich erforderliche minimale Schutzebene festlegen.

Betrachten Sie die Klasse im folgenden Codebeispiel.

[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;  
}  

In diesem Beispiel ist der recordID-Header nicht geschützt, patientName ist signed und SSN ist verschlüsselt und signiert. Auf mindestens einen Textteil, medicalHistory, wurde EncryptAndSign angewandt. Daher ist der gesamte Nachrichtentext verschlüsselt und signiert, auch wenn die Kommentare und Diagnosetextteile niedrigere Schutzebenen festlegen.

SOAP-Aktion

SOAP- und verwandte Webdienststandards definieren eine Eigenschaft mit dem Namen Action, die für jede gesendete SOAP-Nachricht vorhanden sein kann. Die Eigenschaften OperationContractAttribute.Action und OperationContractAttribute.ReplyAction des Vorgangs kontrollieren den Wert dieser Eigenschaft.

SOAP-Header-Attribute

Der SOAP-Standard definiert die folgenden Attribute, die in einem Header verwendet werden können:

  • Actor/Role (Actor in SOAP 1.1, Role in SOAP 1.2)

  • MustUnderstand

  • Relay

Das Actor-Attribut oder das Role-Attribut legt den URI (Uniform Resource Identifier) des Knotens fest, für den ein bestimmter Header angegeben wurde. Das MustUnderstand-Attribut gibt an, ob der Header die Knotenverarbeitung versteht. Das Relay-Attribut gibt an, ob der Header an Downstreamknoten weitergeleitet werden soll. WCF verarbeitet diese Attribute bei eingehenden Nachrichten nicht, mit Ausnahme des MustUnderstand-Attributs (wie weiter unten in diesem Artikel im Abschnitt „Versionsverwaltung für Nachrichtenverträge“ erläutert). Allerdings wird es Ihnen ermöglicht, diese Attribute wie erforderlich zu lesen und zu schreiben (wie in der folgenden Beschreibung).

Beim Versand einer Nachricht werden diese Attribute nicht standardmäßig ausgegeben. Sie können diese auf zwei Arten ändern: Sie können die Attribute statisch auf einen gewünschten Wert festlegen, indem Sie die Eigenschaften MessageHeaderAttribute.Actor, MessageHeaderAttribute.MustUnderstand und MessageHeaderAttribute.Relay ändern (siehe folgendes Codebeispiel). (Beachten Sie, dass es keine Role-Eigenschaft gibt; die Einrichtung der Actor-Eigenschaft gibt das Role-Attribut aus, wenn Sie SOAP 1.2 verwenden).

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

Darüber hinaus können Sie diese Attribute dynamisch per Code kontrollieren. Dies können Sie erreichen, indem Sie den gewünschten Headertyp im MessageHeader<T>-Typ umschließen (nicht mit dem nichtgenerischen Typ zu verwechseln) und den Typ zusammen mit dem MessageHeaderAttribute verwenden. Daraufhin können Sie die Eigenschaften auf MessageHeader<T> nutzen, um die SOAP-Attribute einzurichten (siehe folgendes Codebeispiel).

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

Wenn Sie sowohl den dynamischen als auch den statischen Kontrollmechanismus verwenden, werden die statischen Einstellungen als Standard verwendet, wobei diese später allerdings mithilfe des dynamischen Mechanismus überschrieben werden können (siehe folgenden Code).

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

Das Erstellen von wiederholten Headern mit einem dynamischen Attributsteuerelement ist zulässig (siehe folgenden Code).

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

Wenn Sie diese SOAP-Attribute auf Empfängerseite lesen möchten, muss die MessageHeader<T>-Klasse für den Header im Typ verwendet werden. Überprüfen Sie die Eigenschaften Actor, Relay oder MustUnderstand des Headers des MessageHeader<T>-Typs, um die Attributeinstellungen für die empfangene Nachricht anzuzeigen.

Wenn eine Nachricht empfangen und dann zurückgesendet wird, werden die SOAP-Attributeinstellungen nur für Header des MessageHeader<T>-Typs wiederhergestellt.

Reihenfolge von SOAP-Textteilen

In einigen Fällen müssen Sie die Reihenfolge der Textteile kontrollieren. Die Reihenfolge der Textelemente ist standardmäßig alphabetisch, kann aber über die MessageBodyMemberAttribute.Order-Eigenschaft gesteuert werden. Diese Eigenschaft verfügt über dieselbe Semantik wie die Eigenschaft DataMemberAttribute.Order, abgesehen vom Verhalten im Vererbungsszenario (in Nachrichtenverträgen werden Textmember vom Basistyp nicht vor den Textmembern des abgeleiteten Typs sortiert). Weitere Informationen finden Sie unter Datenmemberreihenfolge.

Im folgenden Beispiel käme amount normalerweise zuerst, da es alphabetisch an erster Stelle steht. Die Order-Eigenschaft setzt es jedoch an die dritte 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;  
}  

Versionsverwaltung für Nachrichtenverträge

Sie müssen möglicherweise gelegentlich Nachrichtenverträge ändern. Beispielsweise kann eine neue Version Ihrer Anwendung einen zusätzlichen Header zu einer Nachricht hinzufügen. Erfolgt dann ein Versand von der neuen Version zur alten, muss das System den zusätzlichen Header und einen fehlenden Header (in der anderen Richtung) verarbeiten.

Für Versionsheader gelten die folgenden Regeln:

  • WCF ignoriert die fehlenden Header. Die entsprechenden Member behalten Ihre Standardwerte.

  • WCF ignoriert auch unerwartete zusätzliche Header. Die Ausnahme zu dieser Regel besteht darin, dass der zusätzliche Header über ein MustUnderstand-Attribut verfügt, das in der eingehenden SOAP-Nachricht auf true festgelegt ist – in diesem Fall wird eine Ausnahme ausgelöst, da ein Header, der verstanden werden muss, nicht verarbeitet werden kann.

Nachrichtentexte haben ähnliche Versionsregeln – sowohl fehlende als auch zusätzliche Nachrichtentextteile werden ignoriert.

Überlegungen zur Vererbung

Ein Nachrichtenvertragstyp kann von einem anderen Typ erben, solange der Basistyp ebenfalls über einen Nachrichtenvertrag verfügt.

Bei der Erstellung einer Nachricht oder beim Zugriff darauf mithilfe eines Nachrichtenvertragstyps, der von anderen Nachrichtenvertragstypen erbt, gelten die folgenden Regeln.

  • Alle Nachrichtenheader in der Vererbungshierarchie werden gesammelt, um den vollständigen Satz an Headern für die Nachricht zu bilden.

  • Alle Nachrichtentextteile in der Vererbungshierarchie werden gesammelt, um den vollständigen Nachrichtentext zu bilden. Die Textteile werden basierend auf den üblichen Sortierungsregeln sortiert (nach der MessageBodyMemberAttribute.Order-Eigenschaft und danach alphabetisch), wobei die Position in der Vererbungshierarchie unbeachtet bleibt. Wenn Nachrichtentextteile in mehreren Ebenen der Vererbungsstruktur auftauchen, wird von der Verwendung der Nachrichtenvertragsvererbung strengstens abgeraten. Wenn eine Basisklasse und eine abgeleitete Klasse einen Header oder einen Textteil mit demselben Namen definieren, wird der Member der Basisklasse verwendet, um den Wert des Headers oder des Textteils zu speichern.

Betrachten Sie die Klassen im folgenden Codebeispiel.

[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;  
}  

Die PatientRecord-Klasse beschreibt eine Nachricht mit einem Header mit dem Namen ID. Der Header entspricht der personID und nicht dem patientID-Member, da der Basismember ausgewählt wird. Somit ist das patientID-Feld in diesem Fall unbrauchbar. Der Text der Nachricht enthält das diagnosis-Element, gefolgt vom patientName-Element, da dies die alphabetische Reihenfolge ist. Beachten Sie, dass das Beispiel ein Muster zeigt, von dem strengstens abgeraten wird: sowohl der Basisklassenvertrag als auch der abgeleitete Klassenvertrag verfügen über Nachrichtentextteile.

Überlegungen zu WSDL

Bei der Erstellung eines WSDL (Web Services Description Language)-Vertrags aus einem Dienst, der Nachrichtenverträge nutzt, ist es wichtig, daran zu denken, dass nicht alle Nachrichtenvertragsfunktionen in der resultierenden WSDL widergespiegelt werden. Beachten Sie folgende Punkte:

  • WSDL kann den Begriff eines Arrays aus Headern nicht ausdrücken. Bei der Erstellung von Nachrichten mit einem Array aus Headern mithilfe des MessageHeaderArrayAttribute spiegelt die resultierende WSDL nur einen Header wider anstelle eines Array.

  • Das resultierende WSDL-Dokument spiegelt möglicherweise einige der Informationen auf Schutzebene nicht wider.

  • Der in der WSDL generierte Nachrichtentyp verfügt über denselben Namen wie die Klasse des Nachrichtenvertragstyps.

  • Bei Verwendung desselben Nachrichtenvertrags in mehreren Vorgängen werden mehrere Nachrichtentypen im WSDL-Dokument generiert. Die Namen werden durch Hinzufügen der Zahlen "2", "3" usw. für eine aufeinanderfolgende Verwendung eindeutig gekennzeichnet. Beim Rückimport der WSDL werden mehrere Nachrichtenvertragstypen erstellt, die abgesehen von ihren Namen identisch sind.

Überlegungen zur SOAP-Codierung

WCF ermöglicht es Ihnen, das ältere SOAP-Codierungsformat für XML zu verwenden. Allerdings wird dies nicht empfohlen. Bei der Verwendung dieses Formats (durch Festlegen der Use-Eigenschaft auf Encoded auf dem System.ServiceModel.XmlSerializerFormatAttribute, das auf den Dienstvertrag angewandt wird), gelten die folgenden zusätzlichen Überlegungen:

  • Die Nachrichtenheader werden nicht unterstützt. Dies bedeutet, dass das Attribut MessageHeaderAttribute und das Arrayattribut MessageHeaderArrayAttribute nicht mit der SOAP-Codierung kompatibel sind.

  • Wird der Nachrichtenvertrag nicht umbrochen, d. h., ist die Eigenschaft IsWrapped auf false festgelegt, kann der Nachrichtenvertrag nur über einen Textteil verfügen.

  • Der Name des Wrapperelements für den Anforderungsnachrichtenvertrag muss zum Vorgangsnamen passen. Verwenden Sie hierfür die WrapperName-Eigenschaft des Nachrichtenvertrags.

  • Der Name des Wrapperelements für den Antwortnachrichtenvertrag muss dem Namen des Vorgangs entsprechen, der über das Suffix "Response" verfügt. Verwenden Sie hierfür die WrapperName-Eigenschaft des Nachrichtenvertrags.

  • SOAP-Codierung behält Objektverweise bei. Betrachten Sie hierzu den folgenden Beispielcode:

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

Nach der Serialisierung der Nachricht mithilfe von SOAP-Codierung enthalten changedFrom und changedTo nicht ihre eigenen Kopien von p, sondern verweisen auf die Kopie innerhalb des changedBy-Elements.

Überlegungen zur Leistung

Jeder Nachrichtenheader und jeder Nachrichtentextteil wird unabhängig von den anderen serialisiert. Deshalb können die gleichen Namespaces wieder für jeden Header und jeden Textteil deklariert werden. Zur Verbesserung der Leistung, insbesondere in Bezug auf die Größe der zu übertragenden Nachricht, fassen Sie mehrere Header und Textteile in einem einzelnen Header oder einzelnen Textteil zusammen. Beispielsweise anstelle des folgenden Codes:

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

Verwenden Sie diesen Code.

[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;  
}  

Ereignisbasiertes asynchrones Modell und Nachrichtenverträge

Die Entwurfsrichtlinien für das ereignisbasierte asynchrone Modell besagen, dass in den Fällen, in denen mehr als ein Wert zurückgegeben wird, ein Wert in der Result-Eigenschaft und die übrigen Werte in Eigenschaften des EventArgs-Objekts zurückgegeben werden sollen. Wenn ein Client Metadaten mithilfe der ereignisbasierten asynchronen Befehlsoptionen importiert, und der Vorgang mehr als einen Wert zurückgibt, dann gibt das EventArgs-Standardobjekt infolgedessen einen Wert in der Result-Eigenschaft zurück, während die übrigen Werte in Eigenschaften des EventArgs-Objekts zurückgegeben werden.

Wenn das Nachrichtenobjekt in der Result-Eigenschaft und die Rückgabewerte als Eigenschaften dieses Objekts übermittelt werden sollen, verwenden Sie die Befehlsoption /messageContract. Damit wird eine Signatur generiert, bei der die Antwortnachricht in der Result-Eigenschaft des EventArgs-Objekts zurückgegeben wird. Alle internen Rückgabewerte sind dann Eigenschaften des Antwortnachrichtenobjekts.

Siehe auch