Verwendung von Nachrichtenverträgen
Üblicherweise achten Entwickler bei der Erstellung von Windows Communication Foundation (WCF)-Anwendungen streng auf die Datenstrukturen und Serialisierungsprobleme und müssen nicht auf die 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 Angeben von 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, einen Typ für einen Parameter oder einen Rückgabewert zu verwenden, der direkt in die benötigte SOAP-Nachricht serialisiert.
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 im Remoteprozeduraufruf (RPC; Remote Procedure Call)-Stil oder im Messagingstil erstellt werden. Bei einem Vorgang im RPC-Stil können Sie jeden serialisierbaren Typ verwenden, und Ihnen stehen die Funktionen zur Verfügung, die für lokale Aufrufe verfügbar sind, beispielsweise mehrere Parameter sowie die Parameter ref und out. In diesem Stil kontrolliert die gewählte Form der Serialisierung die Struktur der Daten in den zugrunde liegenden Nachrichten, aber die WCF-Laufzeit erstellt die Nachrichten selbst, 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 über zu Datenverträgen finden Sie unter Verwenden von Datenverträgen.
Zuweilen ist es allerdings notwendig, genau kontrollieren zu können, wie ein Typ einer SOAP-Nachricht, die übertragen wird, zugeordnet wird. 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. 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, die über Nachrichtenverträge verfügen, 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.
Die folgenden Codes stellen 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 ein SOAP-Umschlag mit erweiterten Headern namens operation
und transactionDate
generiert, die die Inhalte der Felder operation
und transactionDate
enthalten. Der SOAP-Text besteht aus einem Wrapperelement, das Elemente namens sourceAccount and targetAccount
des Account
-Datenvertragstyps und amount
des ganzzahligen Typs enthält.
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.
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. |
Verwendung von benutzerdefinierten Typen innerhalb von Nachrichtenverträgen
Jeder einzelne Nachrichtenheader und Nachrichtentextteil wird mithilfe des ausgewählten Serialisierungsmoduls für den Dienstvertrag, bei dem die Nachricht verwendet wird, serialisiert (in XML konvertiert). Das Standardserialisierungsmodul, 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 einem anderen Serialisierungsmodul, 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 System.ServiceModel.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. Die Standardeinstellung ist 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.
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 System.ServiceModel.MessageContractMemberAttribute.Name und System.ServiceModel.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.
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 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>
' </HelloGreetingMessage>
' </s:Body>
' </s:Envelope>
'
[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>
*/
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. |
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 System.ServiceModel.OperationContractAttribute.Action und System.ServiceModel.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 weitergegeben werden soll. WCF verarbeitet die Attribute bei eingehenden Nachrichten nicht, mit Ausnahme des Attributs MustUnderstand (wie später in diesem Thema unter Abschnitt "Versionsvergabe für Nachrichtenverträge" angegeben). 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 System.ServiceModel.MessageHeaderAttribute.Actor, System.ServiceModel.MessageHeaderAttribute.MustUnderstand und System.ServiceModel.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-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 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).
[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'
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-Klasse für den Header im Typ verwendet werden. Überprüfen Sie die Eigenschaften Actor, Relay oder MustUnderstand des Headers des MessageHeader-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-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 System.ServiceModel.MessageBodyMemberAttribute.Order-Eigenschaft gesteuert werden. Diese Eigenschaft verfügt über dieselbe Semantik wie die Eigenschaft System.Runtime.Serialization.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 Datenmember-Reihenfolge.
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;
}
Versionsvergabe 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 widersetzt sich dem fehlenden Header nicht. 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 System.ServiceModel.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. Berücksichtigen Sie die folgenden 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. Beachten Sie beispielsweise folgenden Code.
[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 zurückgegeben wird und die übrigen Werte in Eigenschaften des EventArgs-Objekts zurückgegeben werden. 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
Konzepte
Verwenden von Datenverträgen
Entwerfen und Implementieren von Diensten