Używanie kontraktów komunikatu

Zazwyczaj podczas tworzenia aplikacji programu Windows Communication Foundation (WCF) deweloperzy zwracają szczególną uwagę na struktury danych i problemy z serializacji i nie muszą martwić się strukturą komunikatów, w których są przenoszone dane. W przypadku tych aplikacji tworzenie kontraktów danych dla parametrów lub wartości zwracanych jest proste. (Aby uzyskać więcej informacji, zobacz Określanie transferu danych w kontraktach usług).

Jednak czasami pełna kontrola nad strukturą komunikatu SOAP jest równie ważna, jak kontrola nad jego zawartością. Jest to szczególnie istotne, gdy współdziałanie jest ważne lub w szczególności kontrolować problemy z zabezpieczeniami na poziomie komunikatu lub części komunikatu. W takich przypadkach można utworzyć kontrakt komunikatów, który umożliwia określenie struktury wymaganego dokładnego komunikatu PROTOKOŁU SOAP.

W tym temacie omówiono sposób użycia różnych atrybutów kontraktu komunikatów w celu utworzenia określonego kontraktu komunikatów dla operacji.

Używanie kontraktów komunikatów w operacjach

Program WCF obsługuje operacje modelowane w stylu zdalnego wywołania procedury (RPC) lub stylu obsługi komunikatów. W operacji w stylu RPC można użyć dowolnego typu z możliwością serializacji i masz dostęp do funkcji dostępnych dla wywołań lokalnych, takich jak wiele parametrów i refout parametrów. W tym stylu forma serializacji wybierana steruje strukturą danych w podstawowych komunikatach, a środowisko uruchomieniowe programu WCF tworzy komunikaty do obsługi operacji. Dzięki temu deweloperzy, którzy nie znają komunikatów protokołu SOAP i SOAP, mogą szybko i łatwo tworzyć aplikacje usług i korzystać z nich.

Poniższy przykład kodu przedstawia operację usługi modelowaną w stylu RPC.

[OperationContract]  
public BankingTransactionResponse PostBankingTransaction(BankingTransaction bt);  

Zwykle kontrakt danych jest wystarczający do zdefiniowania schematu komunikatów. Na przykład w poprzednim przykładzie jest wystarczająca dla większości aplikacji, jeśli BankingTransaction i BankingTransactionResponse mają kontrakty danych, aby zdefiniować zawartość bazowych komunikatów PROTOKOŁU SOAP. Aby uzyskać więcej informacji na temat kontraktów danych, zobacz Using Data Contracts (Używanie kontraktów danych).

Jednak od czasu do czasu konieczne jest dokładne sterowanie strukturą komunikatu SOAP przesyłanego przez przewód. Najbardziej typowym scenariuszem jest wstawianie niestandardowych nagłówków protokołu SOAP. Innym typowym scenariuszem jest zdefiniowanie właściwości zabezpieczeń nagłówków i treści wiadomości, czyli określenie, czy te elementy są podpisane cyfrowo i zaszyfrowane. Na koniec niektóre stosy protokołu SOAP innych firm wymagają, aby komunikaty w określonym formacie. Operacje w stylu obsługi komunikatów zapewniają tę kontrolkę.

Operacja stylu obsługi komunikatów ma co najwyżej jeden parametr i jedną wartość zwracaną, w której oba typy są typami komunikatów; oznacza to, że serializują bezpośrednio do określonej struktury komunikatów SOAP. Może to być dowolny typ oznaczony typem MessageContractAttribute lub Message . Poniższy przykład kodu przedstawia operację podobną do poprzedniego stylu RCP, ale korzystającą ze stylu obsługi komunikatów.

Jeśli na przykład BankingTransaction oba BankingTransactionResponse typy są kontraktami komunikatów, kod w poniższych operacjach jest prawidłowy.

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

Poniższy kod jest jednak nieprawidłowy.

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

Wyjątek jest zgłaszany dla każdej operacji, która obejmuje typ kontraktu komunikatu i która nie jest wykonywana zgodnie z jednym z prawidłowych wzorców. Oczywiście operacje, które nie obejmują typów kontraktów komunikatów, nie podlegają tym ograniczeniom.

Jeśli typ ma zarówno kontrakt komunikatu, jak i kontrakt danych, tylko jego kontrakt komunikatu jest brany pod uwagę, gdy typ jest używany w operacji.

Definiowanie kontraktów komunikatów

Aby zdefiniować kontrakt komunikatu dla typu (tj. w celu zdefiniowania mapowania między typem a kopertą protokołu SOAP), zastosuj element MessageContractAttribute do typu. Następnie zastosuj MessageHeaderAttribute element do tych elementów członkowskich typu, które chcesz umieścić w nagłówkach protokołu SOAP, i zastosuj MessageBodyMemberAttribute element do tych elementów członkowskich, które mają zostać wprowadzone w części treści komunikatu PROTOKOŁU SOAP.

Poniższy kod zawiera przykład użycia kontraktu komunikatów.

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

W przypadku używania tego typu jako parametru operacji jest generowana następująca koperta PROTOKOŁU SOAP:

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

Zwróć uwagę, że operation nagłówki protokołu SOAP i transactionDate treść protokołu SOAP składają się z elementu BankingTransaction otoki zawierającego sourceAccountelement ,targetAccount i amount.

Można zastosować MessageHeaderAttribute wartości i MessageBodyMemberAttribute do wszystkich pól, właściwości i zdarzeń, niezależnie od tego, czy są publiczne, prywatne, chronione, czy wewnętrzne.

Element MessageContractAttribute umożliwia określenie atrybutów WrapperName i WrapperNamespace, które kontrolują nazwę elementu otoki w treści komunikatu PROTOKOŁU SOAP. Domyślnie nazwa typu kontraktu komunikatu jest używana dla otoki, a przestrzeń nazw, w której zdefiniowano http://tempuri.org/ kontrakt komunikatu, jest używany jako domyślna przestrzeń nazw.

Uwaga

KnownTypeAttribute atrybuty są ignorowane w kontraktach komunikatów. KnownTypeAttribute Jeśli element jest wymagany, umieść go w operacji, która używa danego kontraktu komunikatu.

Kontrolowanie nazw i przestrzeni nazw części nagłówka i treści

W reprezentacji protokołu SOAP kontraktu komunikatów każda część nagłówka i treści jest mapowania na element XML, który ma nazwę i przestrzeń nazw.

Domyślnie przestrzeń nazw jest taka sama jak przestrzeń nazw kontraktu usługi, w której uczestniczy komunikat, a nazwa jest określana przez nazwę elementu członkowskiego, do którego MessageHeaderAttribute są stosowane atrybuty lub MessageBodyMemberAttribute .

Te wartości domyślne można zmienić, manipulując elementami MessageContractMemberAttribute.Name i MessageContractMemberAttribute.Namespace (w klasie nadrzędnej atrybutów MessageHeaderAttribute i MessageBodyMemberAttribute ).

Rozważ klasę w poniższym przykładzie kodu.

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

W tym przykładzie IsAudited nagłówek znajduje się w przestrzeni nazw określonej w kodzie, a część treści reprezentująca theData element członkowski jest reprezentowany przez element XML o nazwie transactionData. Poniżej przedstawiono kod XML wygenerowany dla tego kontraktu komunikatu.

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

Kontrolowanie, czy części ciała SOAP są opakowane

Domyślnie części ciała SOAP są serializowane wewnątrz opakowanego elementu. Na przykład poniższy kod przedstawia HelloGreetingMessage element otoki wygenerowany na podstawie nazwy MessageContractAttribute typu w kontrakcie komunikatu dla komunikatu HelloGreetingMessage .

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

Aby pominąć element otoki, ustaw IsWrapped właściwość na false. Aby kontrolować nazwę i przestrzeń nazw elementu otoki, użyj WrapperName właściwości i WrapperNamespace .

Uwaga

Posiadanie więcej niż jednej części treści komunikatów w komunikatach, które nie są opakowane, nie jest zgodne z profilem WS-I Basic Profile 1.1 i nie jest zalecane podczas projektowania nowych kontraktów komunikatów. Jednak może być konieczne posiadanie więcej niż jednej części treści komunikatów niezapisanych w określonych scenariuszach współdziałania. Jeśli zamierzasz przesyłać więcej niż jeden element danych w treści komunikatu, zaleca się użycie domyślnego (opakowanego) trybu. Posiadanie więcej niż jednego nagłówka komunikatów w niezapisanych komunikatach jest całkowicie akceptowalne.

Używanie typów niestandardowych wewnątrz kontraktów komunikatów

Każda pojedyncza część nagłówka i treści wiadomości jest serializowana (przekształcona w xml) przy użyciu wybranego aparatu serializacji dla kontraktu usługi, w którym jest używany komunikat. Domyślny aparat serializacji , XmlFormatter, może obsługiwać dowolny typ, który ma kontrakt danych, jawnie (przez ) System.Runtime.Serialization.DataContractAttributelub niejawnie (przez bycie typem pierwotnym, o System.SerializableAttributewartości i tak dalej). Aby uzyskać więcej informacji, zobacz Using Data Contracts (Korzystanie z kontraktów danych).

W poprzednim przykładzie Operation typy i BankingTransactionData muszą mieć kontrakt danych i transactionDate można je serializować, ponieważ DateTime jest to typ pierwotny (i ma niejawny kontrakt danych).

Można jednak przełączyć się na inny aparat serializacji, czyli XmlSerializer. Jeśli ustawisz taki przełącznik, upewnij się, że wszystkie typy używane dla nagłówków komunikatów i części treści są serializowalne przy użyciu elementu XmlSerializer.

Używanie tablic wewnątrz kontraktów komunikatów

Tablice powtarzających się elementów można używać w kontraktach komunikatów na dwa sposoby.

Pierwszą z nich jest użycie obiektu MessageHeaderAttribute lub MessageBodyMemberAttribute bezpośrednio w tablicy. W takim przypadku cała tablica jest serializowana jako jeden element (czyli jeden nagłówek lub jedna część treści) z wieloma elementami podrzędnymi. Rozważ klasę w poniższym przykładzie.

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

Powoduje to, że nagłówki protokołu SOAP są podobne do poniższych.

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

Alternatywą dla tej metody jest użycie elementu MessageHeaderArrayAttribute. W takim przypadku każdy element tablicy jest serializowany niezależnie i tak, aby każdy element tablicy miał jeden nagłówek, podobny do poniższego.

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

Domyślna nazwa wpisów tablicy to nazwa elementu członkowskiego, do którego MessageHeaderArrayAttribute są stosowane atrybuty.

Atrybut MessageHeaderArrayAttribute dziedziczy z elementu MessageHeaderAttribute. Ma on taki sam zestaw funkcji, jak atrybuty inne niż tablica, na przykład można ustawić kolejność, nazwę i przestrzeń nazw dla tablicy nagłówków w taki sam sposób, jak dla pojedynczego nagłówka. Jeśli używasz Order właściwości w tablicy, ma ona zastosowanie do całej tablicy.

Można zastosować MessageHeaderArrayAttribute tylko do tablic, a nie kolekcji.

Używanie tablic bajtów w kontraktach komunikatów

Tablice bajtów, w przypadku użycia z atrybutami innych niż tablica (MessageBodyMemberAttribute i MessageHeaderAttribute), nie są traktowane jako tablice, ale jako specjalny typ pierwotny reprezentowany jako dane zakodowane w formacie Base64 w wynikowym kodzie XML.

W przypadku używania tablic bajtowych z atrybutem MessageHeaderArrayAttributetablicy wyniki zależą od używanego serializatora. Z domyślnym serializatorem tablica jest reprezentowana jako pojedynczy wpis dla każdego bajtu. Jednak po wybraniu XmlSerializer obiektu (przy użyciu XmlSerializerFormatAttribute kontraktu usługi) tablice bajtów są traktowane jako dane Base64 niezależnie od tego, czy są używane atrybuty tablicy, czy nie tablicy.

Podpisywanie i szyfrowanie części wiadomości

Kontrakt komunikatu może wskazywać, czy nagłówki i/lub treść wiadomości powinny być podpisane cyfrowo i zaszyfrowane.

Można to zrobić, ustawiając MessageContractMemberAttribute.ProtectionLevel właściwość na atrybutach MessageHeaderAttribute i MessageBodyMemberAttribute . Właściwość jest wyliczaniem System.Net.Security.ProtectionLevel typu i może być ustawiona na None (bez szyfrowania lub podpisu), Sign (tylko podpis cyfrowy) lub EncryptAndSign (szyfrowanie i podpis cyfrowy). Wartość domyślna to EncryptAndSign.

Aby te funkcje zabezpieczeń działały, należy prawidłowo skonfigurować powiązania i zachowania. Jeśli używasz tych funkcji zabezpieczeń bez odpowiedniej konfiguracji (na przykład próbujesz podpisać komunikat bez podawania poświadczeń), w czasie weryfikacji zostanie zgłoszony wyjątek.

W przypadku nagłówków wiadomości poziom ochrony jest określany indywidualnie dla każdego nagłówka.

W przypadku części treści komunikatów poziom ochrony można traktować jako "minimalny poziom ochrony". Organizm ma tylko jeden poziom ochrony, niezależnie od liczby części ciała. Poziom ochrony ciała jest określany przez najwyższe ProtectionLevel ustawienie właściwości wszystkich części ciała. Należy jednak ustawić poziom ochrony każdej części ciała na rzeczywisty wymagany minimalny poziom ochrony.

Rozważ klasę w poniższym przykładzie kodu.

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

W tym przykładzie recordID nagłówek nie jest chroniony, patientName to signed, i SSN jest szyfrowany i podpisany. Co najmniej jedna część treści, medicalHistory, została zastosowana EncryptAndSign , a tym samym cała treść wiadomości jest zaszyfrowana i podpisana, mimo że komentarze i części ciała diagnozy określają niższe poziomy ochrony.

Akcja protokołu SOAP

Standardy protokołu SOAP i powiązanych usług sieci Web definiują właściwość o nazwie Action , która może być obecna dla każdego wysłanego komunikatu PROTOKOŁU SOAP. Właściwości i OperationContractAttribute.ReplyAction operacji OperationContractAttribute.Action kontrolują wartość tej właściwości.

Atrybuty nagłówka protokołu SOAP

Standard PROTOKOŁU SOAP definiuje następujące atrybuty, które mogą istnieć w nagłówku:

  • Actor/Role (Actor w soap 1.1, Role w SOAP 1.2)

  • MustUnderstand

  • Relay

Atrybut Actor or Role określa identyfikator URI (Uniform Resource Identifier) węzła, dla którego ma być przeznaczony dany nagłówek. Atrybut MustUnderstand określa, czy węzeł przetwarzający nagłówek musi go zrozumieć. Atrybut Relay określa, czy nagłówek ma być przekazywany do węzłów podrzędnych. Program WCF nie wykonuje żadnego przetwarzania tych atrybutów w komunikatach przychodzących, z wyjątkiem atrybutu określonego MustUnderstand w sekcji "Obsługa wersji kontraktu komunikatów" w dalszej części tego tematu. Jednak umożliwia odczytywanie i zapisywanie tych atrybutów w razie potrzeby, jak w poniższym opisie.

Podczas wysyłania komunikatu te atrybuty nie są domyślnie emitowane. Można to zmienić na dwa sposoby. Najpierw można statycznie ustawić atrybuty na dowolne żądane wartości, zmieniając MessageHeaderAttribute.Actorwłaściwości , MessageHeaderAttribute.MustUnderstandi MessageHeaderAttribute.Relay , jak pokazano w poniższym przykładzie kodu. (Pamiętaj, że nie Role ma właściwości; ustawienie Actor właściwości emituje Role atrybut, jeśli używasz protokołu 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;  
}  

Drugi sposób kontrolowania tych atrybutów odbywa się dynamicznie za pomocą kodu. Można to osiągnąć, opakowując żądany typ nagłówka w typie MessageHeader<T> (nie należy mylić tego typu z wersją niegeneryjną) i używając typu razem z MessageHeaderAttribute. Następnie możesz użyć właściwości w elemecie MessageHeader<T> , aby ustawić atrybuty protokołu SOAP, jak pokazano w poniższym przykładzie kodu.

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

Jeśli używasz zarówno mechanizmów dynamicznych, jak i statycznych, ustawienia statyczne są używane jako domyślne, ale później można je zastąpić przy użyciu mechanizmu dynamicznego, jak pokazano w poniższym kodzie.

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

Tworzenie powtarzających się nagłówków z kontrolką atrybutów dynamicznych jest dozwolone, jak pokazano w poniższym kodzie.

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

Po stronie odbierania odczytywanie tych atrybutów protokołu SOAP można wykonać tylko wtedy, gdy MessageHeader<T> klasa jest używana dla nagłówka w typie. ActorSprawdź właściwości , Relaylub MustUnderstand nagłówka MessageHeader<T> typu, aby odnaleźć ustawienia atrybutu w odebranym komunikacie.

Po odebraniu komunikatu, a następnie wysłaniu z powrotem, ustawienia atrybutu PROTOKOŁU SOAP przechodzą tylko w obie strony dla nagłówków MessageHeader<T> typu.

Kolejność części ciała soap

W niektórych okolicznościach może być konieczne kontrolowanie kolejności części ciała. Kolejność elementów treści jest domyślnie alfabetyczna, ale może być kontrolowana MessageBodyMemberAttribute.Order przez właściwość . Ta właściwość ma taką samą semantykę jak DataMemberAttribute.Order właściwość, z wyjątkiem zachowania w scenariuszach dziedziczenia (w kontraktach komunikatów składowe treści typu podstawowego nie są sortowane przed pochodnymi składowymi treści typu). Aby uzyskać więcej informacji, zobacz Data Member Order (Kolejność składowych danych).

W poniższym przykładzie zwykle jest to pierwsze, amount ponieważ jest to pierwszy alfabetycznie. Order Jednak właściwość umieszcza ją na trzeciej pozycji.

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

Przechowywanie wersji kontraktu komunikatów

Czasami może być konieczne zmianę kontraktów komunikatów. Na przykład nowa wersja aplikacji może dodać dodatkowy nagłówek do komunikatu. Następnie podczas wysyłania z nowej wersji do starej system musi zajmować się dodatkowym nagłówkiem, a także brakującym nagłówkiem podczas przechodzenia w innym kierunku.

Następujące reguły dotyczą nagłówków przechowywania wersji:

  • Program WCF nie sprzeciwia się brakującym nagłówkom — odpowiednie elementy członkowskie pozostają w ich wartościach domyślnych.

  • Program WCF ignoruje również nieoczekiwane dodatkowe nagłówki. Jednym wyjątkiem od tej reguły jest to, że dodatkowy nagłówek ma MustUnderstand atrybut ustawiony na true w przychodzącym komunikacie PROTOKOŁU SOAP — w tym przypadku zgłaszany jest wyjątek, ponieważ nie można przetworzyć nagłówka, który musi być zrozumiały.

Treść komunikatów ma podobne reguły przechowywania wersji — brakujące i dodatkowe części treści komunikatów są ignorowane.

Zagadnienia dotyczące dziedziczenia

Typ kontraktu komunikatu może dziedziczyć z innego typu, o ile typ podstawowy ma również kontrakt komunikatu.

Podczas tworzenia lub uzyskiwania dostępu do komunikatu przy użyciu typu kontraktu komunikatów dziedziczonego z innych typów kontraktów komunikatów obowiązują następujące reguły:

  • Wszystkie nagłówki komunikatów w hierarchii dziedziczenia są zbierane razem w celu utworzenia pełnego zestawu nagłówków komunikatu.

  • Wszystkie części treści komunikatów w hierarchii dziedziczenia są zbierane razem w celu utworzenia pełnej treści komunikatu. Części ciała są uporządkowane zgodnie ze zwykłymi regułami porządkowania (według MessageBodyMemberAttribute.Order właściwości, a następnie alfabetycznymi), bez znaczenia dla ich miejsca w hierarchii dziedziczenia. Korzystanie z dziedziczenia kontraktu komunikatów, w którym części treści komunikatu występują na wielu poziomach drzewa dziedziczenia, jest zdecydowanie odradzane. Jeśli klasa bazowa i klasa pochodna definiują nagłówek lub część treści o tej samej nazwie, składowa z najbardziej bazowej klasy jest używana do przechowywania wartości tego nagłówka lub części treści.

Rozważ klasy w poniższym przykładzie kodu.

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

Klasa PatientRecord opisuje komunikat z jednym nagłówkiem o nazwie ID. Nagłówek odpowiada elementowi członkowskiemu personIDpatientID , a nie elementowi członkowskiemu, ponieważ jest wybierany element członkowski najbardziej bazowy. W związku z patientID tym pole jest bezużyteczne w tym przypadku. Treść komunikatu zawiera diagnosis element, po którym następuje patientName element, ponieważ jest to kolejność alfabetyczna. Zwróć uwagę, że w przykładzie pokazano wzorzec, który jest zdecydowanie zniechęcony: zarówno podstawowe, jak i pochodne kontrakty komunikatów mają części treści komunikatu.

Zagadnienia dotyczące języka WSDL

Podczas generowania kontraktu języka WSDL (Web Services Description Language) z usługi korzystającej z kontraktów komunikatów należy pamiętać, że nie wszystkie funkcje kontraktu komunikatów są odzwierciedlane w wynikowym języku WSDL. Rozważ następujące punkty:

  • WSDL nie może wyrazić koncepcji tablicy nagłówków. Podczas tworzenia komunikatów z tablicą nagłówków przy użyciu MessageHeaderArrayAttribute, wynikowy plik WSDL odzwierciedla tylko jeden nagłówek zamiast tablicy.

  • Wynikowy dokument WSDL może nie odzwierciedlać niektórych informacji na poziomie ochrony.

  • Typ komunikatu wygenerowany w języku WSDL ma taką samą nazwę jak nazwa klasy typu kontraktu komunikatu.

  • W przypadku korzystania z tego samego kontraktu komunikatów w wielu operacjach wiele typów komunikatów jest generowanych w dokumencie WSDL. Nazwy są unikatowe przez dodanie liczb "2", "3" i tak dalej w przypadku kolejnych zastosowań. Podczas importowania z powrotem WSDL tworzone jest wiele typów kontraktów komunikatów i są identyczne z wyjątkiem ich nazw.

Zagadnienia dotyczące kodowania protokołu SOAP

Program WCF umożliwia korzystanie ze starszego stylu kodowania SOAP kodu XML, jednak jego użycie nie jest zalecane. W przypadku korzystania z tego stylu (przez ustawienie Use właściwości na Encoded wartość zastosowanej System.ServiceModel.XmlSerializerFormatAttribute do kontraktu usługi) obowiązują następujące dodatkowe zagadnienia:

  • Nagłówki wiadomości nie są obsługiwane; Oznacza to, że atrybut i atrybut MessageHeaderAttributeMessageHeaderArrayAttribute tablicy są niezgodne z kodowaniem PROTOKOŁU SOAP.

  • Jeśli kontrakt komunikatu nie jest opakowany, oznacza to, że jeśli właściwość IsWrapped jest ustawiona na false, kontrakt komunikatu może mieć tylko jedną część treści.

  • Nazwa elementu otoki dla kontraktu komunikatu żądania musi być zgodna z nazwą operacji. WrapperName W tym celu użyj właściwości kontraktu komunikatów.

  • Nazwa elementu otoki dla kontraktu komunikatu odpowiedzi musi być taka sama jak nazwa operacji sufiksu "Odpowiedź". WrapperName W tym celu użyj właściwości kontraktu komunikatów.

  • Kodowanie protokołu SOAP zachowuje odwołania do obiektów. Rozważmy na przykład następujący kod.

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

Po serializacji komunikatu przy użyciu kodowania changedFrom SOAP i changedTo nie zawierają własnych kopii pelementu , ale zamiast tego wskaż kopię wewnątrz changedBy elementu.

Zagadnienia dotyczące wydajności

Każda część nagłówka i treści wiadomości jest serializowana niezależnie od innych. W związku z tym te same przestrzenie nazw można zadeklarować ponownie dla każdej części nagłówka i treści. Aby poprawić wydajność, zwłaszcza pod względem rozmiaru komunikatu w przewodzie, skonsoliduj wiele nagłówków i części treści w jeden nagłówek lub część treści. Na przykład zamiast następującego kodu:

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

Użyj tego kodu.

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

Asynchroniczne i asynchroniczne kontrakty komunikatów oparte na zdarzeniach

Wytyczne dotyczące projektowania dla modelu asynchronicznego opartego na zdarzeniach stwierdzają, że jeśli zwracana jest więcej niż jedna wartość, jedna wartość jest zwracana jako Result właściwość, a pozostałe są zwracane jako właściwości obiektu EventArgs . Jednym z tych wyników jest to, że jeśli klient importuje metadane przy użyciu opcji polecenia asynchronicznego opartego na zdarzeniach, a operacja zwraca więcej niż jedną wartość, obiekt domyślny EventArgs zwraca jedną wartość jako Result właściwość, a pozostałe są właściwościami EventArgs obiektu.

Jeśli chcesz otrzymać obiekt komunikatu jako Result właściwość i mieć zwrócone wartości jako właściwości tego obiektu, użyj /messageContract opcji polecenia . Spowoduje to wygenerowanie podpisu zwracającego komunikat odpowiedzi jako Result właściwość EventArgs obiektu. Wszystkie wewnętrzne wartości zwracane są właściwościami obiektu komunikatu odpowiedzi.

Zobacz też