Angeben von Datenübertragung in Dienstverträgen

Windows Communication Foundation (WCF) kann man sich als Messaginginfrastruktur vorstellen. Dienstvorgänge können Nachrichten empfangen, sie verarbeiten und ihnen Nachrichten schicken. Nachrichten werden mit Vorgangsverträgen beschrieben. Beispiel:

[ServiceContract]
public interface IAirfareQuoteService
{
    [OperationContract]
    float GetAirfare(string fromCity, string toCity);
}

Hier akzeptiert der GetAirfare-Vorgang eine Nachricht mit Informationen über fromCity und toCity und gibt dann eine Nachricht zurück, die eine Zahl enthält.

In diesem Thema werden die verschiedenen Möglichkeiten erläutert, wie ein Vorgangsvertrag Nachrichten beschreiben kann.

Beschreiben von Nachrichten mithilfe von Parametern

Die einfachste Art zur Beschreibung einer Nachricht ist die Verwendung einer Parameterliste und des Rückgabewerts. Im vorherigen Beispiel wurden der fromCity- und der toCity-Zeichenfolgenparameter zur Beschreibung der Anforderungsnachricht verwendet, und der Gleitkommarückgabewert wurde zur Beschreibung der Antwortnachricht verwendet. Wenn der Rückgabewert allein zur Beschreibung einer Antwortnachricht nicht ausreicht, können out-Parameter verwendet werden. Der folgende Vorgang enthält z. B. fromCity und toCity in der Anforderungsnachricht und eine Zahl zusammen mit einer Währung in der Antwortnachricht:

[OperationContract]
float GetAirfare(string fromCity, string toCity, out string currency);

Sie können zusätzlich Verweisparameter verwenden, um einen Parameter sowohl zu einem Teil der Anforderungs- als auch der Antwortnachricht zu machen. Die Parameter müssen Typen angehören, die serialisiert (zu XML konvertiert) werden können. Standardmäßig verwendet WCF eine Komponente, die als DataContractSerializer-Klasse bezeichnet wird, um diese Konvertierung auszuführen. Die meisten primitiven Typen (z. B. int, string, float und DateTime) werden unterstützt. Benutzerdefinierte Typen müssen normalerweise einen Datenvertrag aufweisen. Weitere Informationen finden Sie unter Verwenden von Datenverträgen.

public interface IAirfareQuoteService
{
    [OperationContract]
    float GetAirfare(Itinerary itinerary, DateTime date);
    }
    [DataContract]
    public class Itinerary
    {
        [DataMember]
        public string fromCity;
        [DataMember]
        public string toCity;
}

Gelegentlich ist das DataContractSerializer nicht zur Serialisierung der Typen geeignet. WCF unterstützt ein alternatives Serialisierungsmodul, XmlSerializer, das Sie zur Serialisierung von Parametern verwenden können. Das XmlSerializer bietet mehr Kontrolle über den resultierenden XML-Code durch Verwendung von Attributen, wie z. B. XmlAttributeAttribute. Um zur Verwendung von XmlSerializer für einen bestimmten Vorgang oder den gesamten Dienst überzugehen, wenden Sie das XmlSerializerFormatAttribute-Attribut auf einen Vorgang oder einen Dienst an. Beispiel:

[ServiceContract]
public interface IAirfareQuoteService
{
    [OperationContract]
    [XmlSerializerFormat]
    float GetAirfare(Itinerary itinerary, DateTime date);
}
public class Itinerary
{
    public string fromCity;
    public string toCity;
    [XmlAttribute]
    public bool isFirstClass;
}

Weitere Informationen finden Sie unter Verwenden der XmlSerializer-Klasse. Vergessen Sie nicht, dass ein manueller Wechsel zum XmlSerializer, wie hier dargestellt, nicht empfohlen wird, wenn nicht triftige Gründe dafür vorliegen, wie sie in diesem Thema ausführlich beschrieben werden.

Zur Isolierung von .NET-Parameternamen von Vertragsnamen können Sie das MessageParameterAttribute-Attribut verwenden. Verwenden Sie die Name-Eigenschaft zur Festlegung des Vertragsnamens. Der folgende Vorgangsvertrag entspricht z. B. dem ersten Beispiel in diesem Thema.

[OperationContract]
public float GetAirfare(
    [MessageParameter(Name=”fromCity”)] string originCity,
    [MessageParameter(Name=”toCity”)] string destinationCity);

Beschreiben von leeren Nachrichten

Eine leere Anforderungsnachricht kann dadurch beschrieben werden, dass sie keine Eingabe- oder Verweisparameter aufweist. Beispiel:

[OperationContract]

public int GetCurrentTemperature();

Eine leere Antwortnachricht kann dadurch beschrieben werden, dass sie einen void-Rückgabetyp und keine Ausgabe- oder Verweisparameter aufweist. Beispiel:

[OperationContract]
public void SetTemperature(int temperature);

Dies unterscheidet sich von einem unidirektionalen Vorgang wie z. B.:

[OperationContract(IsOneWay=true)]
public void SetLightbulbStatus(bool isOn);

Der SetTemperatureStatus-Vorgang gibt eine leere Nachricht zurück. Er gibt stattdessen möglicherweise einen Fehler zurück, wenn es ein Problem beim Verarbeiten der Eingabenachricht gibt. Der SetLightbulbStatus-Vorgang gibt keinen Wert zurück. Es gibt keine Möglichkeit, eine Fehlerbedingung dieses Vorgangs zu übermitteln.

Beschreiben von Nachrichten mithilfe von Nachrichtenverträgen

Sie können einen einzelnen Typ verwenden, um die ganze Nachricht darzustellen. Es ist zwar möglich, für diesen Zweck einen Datenvertrag zu verwenden, empfohlen wird jedoch die Verwendung eines Nachrichtenvertrags – dadurch werden unnötige Wrappingebenen im resultierenden XML-Code vermieden. Darüber hinaus ermöglichen Nachrichtenverträge eine bessere Kontrolle über die resultierenden Nachrichten. Sie können z. B. entscheiden, welche Informationen im Nachrichtentext und welche in den Nachrichtenheadern enthalten sein sollen. Im folgenden Beispiel wird die Verwendung von Nachrichtenverträgen veranschaulicht.

[ServiceContract]
public interface IAirfareQuoteService
{
    [OperationContract]
    GetAirfareResponse GetAirfare(GetAirfareRequest request);
}

[MessageContract]
public class GetAirfareRequest
{
    [MessageHeader] public DateTime date;
    [MessageBodyMember] public Itinerary itinerary;
}

[MessageContract]
public class GetAirfareResponse
{
    [MessageBodyMember] public float airfare;
    [MessageBodyMember] public string currency;
}

[DataContract]
public class Itinerary
{
    [DataMember] public string fromCity;
    [DataMember] public string toCity;
}

Weitere Informationen finden Sie unter Verwendung von Nachrichtenverträgen.

Im vorigen Beispiel wird die DataContractSerializer-Klasse noch standardmäßig verwendet. Die XmlSerializer-Klasse kann auch in Verbindung mit Nachrichtenverträgen verwendet werden. Zu diesem Zweck wenden Sie das XmlSerializerFormatAttribute-Attribut entweder auf den Vorgang oder auf den Vertrag an, und verwenden Sie Typen, die mit der XmlSerializer-Klasse in den Nachrichtenheadern und Textmembern kompatibel sind.

Beschreiben von Nachrichten mithilfe von Streams

Eine andere Möglichkeit zur Beschreibung von Nachrichten in Vorgängen ist die Verwendung der Stream-Klasse oder einer der von ihr abgeleiteten Klasse in einem Vorgangsvertrag oder als Textmember eines Nachrichtenvertrags (es muss sich in diesem Fall um den einzigen Member handeln). Für eingehende Nachrichten muss der Typ Stream sein – es können keine abgeleiteten Klassen verwendet werden.

Statt das Serialisierungsprogramm aufzurufen, ruft WCF Daten aus einem Stream ab und setzt sie direkt in eine ausgehende Nachricht ein oder ruft Daten aus einer eingehenden Nachricht ab und setzt sie direkt in einen Stream ein. Im folgenden Beispiel wird die Verwendung von Streams veranschaulicht.

[OperationContract]
public Stream DownloadFile(string fileName);

Sie können Stream- und Nicht-Stream-Daten in einem einzelnen Nachrichtentext nicht kombinieren. Verwenden Sie einen Nachrichtenvertrag, um die zusätzlichen Daten in Nachrichtenheader einzusetzen. Im folgenden Beispiel wird die falsche Verwendung von Streams bei der Definition des Vorgangsvertrags veranschaulicht.

//Incorrect:
// [OperationContract]
// public void UploadFile (string fileName, Stream fileData);

Im folgenden Beispiel wird die korrekte Verwendung von Streams bei der Definition eines Vorgangsvertrags veranschaulicht.

[OperationContract]
public void UploadFile (UploadFileMessage message);
//code omitted…
[MessageContract]
public class UploadFileMessage
{
    [MessageHeader] public string fileName;
    [MessageBodyMember] public Stream fileData;
}

Weitere Informationen finden Sie unter Umfangreiche Daten und Streaming.

Verwenden der Nachrichtenklasse

Um umfassende programmgesteuerte Kontrolle über gesendete oder empfangene Nachrichten zu haben, können Sie die Message-Klasse direkt verwenden, wie im folgenden Beispielcode dargestellt wird.

[OperationContract]
public void LogMessage(Message m);

Dies ist ein erweitertes Szenario, das ausführlich in Verwenden der Meldungsklasse beschrieben wird.

Beschreiben von Fehlernachrichten

Zusätzlich zu den Nachrichten, die durch den Rückgabewert und Ausgabe- oder Verweisparameter beschrieben werden, kann jeder nicht unidirektionale Vorgang mindestes zwei mögliche Nachrichten zurückgeben: die normale Antwortnachricht und eine Fehlernachricht. Betrachten Sie den folgenden Vorgangsvertrag.

[OperationContract]
float GetAirfare(string fromCity, string toCity, DateTime date);

Dieser Vorgang kann entweder eine normale Nachricht zurückgeben, die eine float-Zahl enthält, oder eine Fehlernachricht, die einen Fehlercode und eine Beschreibung enthält. Dies kann durch Auslösen einer FaultException in der Dienstimplementierung erreicht werden.

Sie können weitere mögliche Fehlermeldungen angeben, indem Sie das FaultContractAttribute-Attribut verwenden. Die zusätzlichen Fehler müssen mit dem DataContractSerializer serialisierbar sein, wie im folgenden Beispielcode gezeigt.

[OperationContract]
[FaultContract(typeof(ItineraryNotAvailableFault))]
float GetAirfare(string fromCity, string toCity, DateTime date);

//code omitted…

[DataContract]
public class ItineraryNotAvailableFault
{
    [DataMember]
    public bool IsAlternativeDateAvailable;

    [DataMember]
    public DateTime alternativeSuggestedDate;
}

Diese zusätzlichen Fehler können durch Auslösen einer FaultException des geeigneten Datenvertragstyps generiert werden. Weitere Informationen finden Sie unter Behandeln von Ausnahmen und Fehlern.

Sie können die XmlSerializer-Klasse nicht verwenden, um Fehler zu beschreiben. Das XmlSerializerFormatAttribute hat keine Auswirkungen auf Fehlerverträge.

Verwenden von abgeleiteten Typen

Sie können einen Basistyp für einen Vorgangs- oder Nachrichtenvertrag verwenden und dann einen abgeleiteten Typ verwenden, wenn Sie den Vorgang tatsächlich aufrufen. In diesem Fall müssen Sie entweder das ServiceKnownTypeAttribute-Attribut oder einen alternativen Mechanismus verwenden, um die Verwendung von abgeleiteten Typen zu ermöglichen. Betrachten Sie den folgenden Vorgang.

[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);

Angenommen, zwei Typen, Book und Magazine, werden aus LibraryItem abgeleitet. Um diese Typen im IsLibraryItemAvailable-Vorgang zu verwenden, können Sie den Vorgang wie folgt ändern:

[OperationContract]

[ServiceKnownType(typeof(Book))]

[ServiceKnownType(typeof(Magazine))]

public bool IsLibraryItemAvailable(LibraryItem item);

Alternativ dazu können Sie, wie im folgenden Beispielcode dargestellt, das KnownTypeAttribute-Attribut verwenden, wenn das Standard-DataContractSerializer verwendet wird.

[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);

// code omitted…

[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryItem
{
    //code omitted…
}

Sie können das XmlIncludeAttribute-Attribut verwenden, wenn Sie XmlSerializer verwenden.

Sie können das ServiceKnownTypeAttribute-Attribut auf einen Vorgang oder auf den gesamten Dienst anwenden. Es akzeptiert entweder einen Typ oder den Namen der Methode, die aufgerufen werden soll, um eine Liste bekannter Typen zu erzeugen, genau wie das KnownTypeAttribute-Attribut. Weitere Informationen finden Sie unter Bekannte Typen in Datenverträgen.

Angeben der Verwendung und des Stils

Bei der Beschreibung von Diensten mithilfe von Web Services Description Language (WSDL) sind die beiden am häufigsten verwendeten Stile der Dokumentstil und der Remoteprozeduraufruf (RPC, remote procedure call). Beim Dokumentstil wird der gesamte Nachrichtentext mithilfe des Schemas beschrieben, und WSDL beschreibt die verschiedenen Nachrichtentextteile durch Verweisen auf Elemente innerhalb dieses Schemas. Beim RPC-Stil verweist WSDL auf einen Schematyp für jeden Nachrichtenteil statt auf ein Element. In einigen Fällen müssen Sie einen dieser Stile manuell auswählen. Zu diesem Zweck können Sie das DataContractFormatAttribute-Attribut anwenden und die Style-Eigenschaft festlegen (wenn das DataContractSerializer verwendet wird), oder Sie legen Style in dem XmlSerializerFormatAttribute-Attribut fest (wenn das XmlSerializer verwendet wird).

Außerdem unterstützt das XmlSerializer zwei Formen von serialisiertem XML: Literal und Encoded. Literal ist die am häufigsten akzeptierte Form und die einzige Form, die vom DataContractSerializer unterstützt wird. Encoded ist eine Legacyform, die in Abschnitt 5 der SOAP-Spezifikation beschrieben wird und die für neue Dienste nicht empfohlen wird. Um zum Encoded-Modus zu wechseln, legen Sie die Use-Eigenschaft für das XmlSerializerFormatAttribute-Attribut auf Encoded fest.

In den meisten Fällen sollten Sie die Standardeinstellungen für die Style- und die Use-Eigenschaft nicht ändern.

Kontrollieren des Serialisierungsprozesses

Es gibt eine Reihe von Möglichkeiten, die Art und Weise anzupassen, in der Daten serialisiert werden.

Ändern der Serverserialisierungseinstellungen

Wenn das Standard-DataContractSerializer verwendet wird, können Sie einige Aspekte des Serialisierungsprozesses für den Dienst durch Anwenden des ServiceBehaviorAttribute-Attributs auf den Dienst steuern. Sie können insbesondere die MaxItemsInObjectGraph-Eigenschaft verwenden, um das Kontingent festzulegen, das die maximale Anzahl an Objekten einschränkt, die das DataContractSerializer deserialisiert. Sie können die IgnoreExtensionDataObject-Eigenschaft verwenden, um das Roundtrip-Versionsverwaltungsfeature zu deaktivieren. Weitere Informationen zu Kontingenten finden Sie unter Sicherheitsüberlegungen zu Daten. Weitere Informationen zu Roundtrips finden Sie unter Aufwärtskompatible Datenverträge.

[ServiceContract]
[ServiceBehavior(MaxItemsInObjectGraph=100000)]
public interface IDataService
{
    [OperationContract] DataPoint[] GetData();
}

Serialisierungsverhalten

In WCF stehen zwei Arten von Verhalten zur Verfügung, das DataContractSerializerOperationBehavior und das XmlSerializerOperationBehavior, die automatisch geladen werden, je nachdem, welches Serialisierungsprogramm für einen bestimmten Vorgang verwendet wird. Da diese Arten von Verhalten automatisch angewendet werden, müssen Sie sie normalerweise nicht beachten.

DataContractSerializerOperationBehavior weist jedoch die MaxItemsInObjectGraph-, die IgnoreExtensionDataObject- und die DataContractSurrogate-Eigenschaften auf, die Sie zur Anpassung des Serialisierungsprozesses verwenden können. Die ersten beiden Eigenschaften haben die gleiche Bedeutung, wie im vorherigen Abschnitt erläutert. Sie können die DataContractSurrogate-Eigenschaft verwenden, um Datenvertrag-Ersatzzeichen zu aktivieren, die ein leistungsfähiges Werkzeug zum Anpassen und Erweitern des Serialisierungsprozesses darstellen. Weitere Informationen finden Sie unter Datenvertrag-Ersatzzeichen.

Sie können das DataContractSerializerOperationBehavior verwenden, um sowohl die Client- als auch die Serverserialisierung anzupassen. Im folgenden Beispiel wird das Erhöhen des MaxItemsInObjectGraph-Kontingents für den Client veranschaulicht.

ChannelFactory<IDataService> factory = new ChannelFactory<IDataService>(binding, address);
foreach (OperationDescription op in factory.Endpoint.Contract.Operations)
{
    DataContractSerializerOperationBehavior dataContractBehavior =
                op.Behaviors.Find<DataContractSerializerOperationBehavior>()
                as DataContractSerializerOperationBehavior;
    if (dataContractBehavior != null)
    {
        dataContractBehavior.MaxItemsInObjectGraph = 100000;
    }
}
IDataService client = factory.CreateChannel();

Es folgt der entsprechende Code für den Dienst, falls er selbst gehostet wird.

ServiceHost serviceHost = new ServiceHost(typeof(IDataService))
foreach (ServiceEndpoint ep in serviceHost.Description.Endpoints)
{
foreach (OperationDescription op in ep.Contract.Operations)
{
        DataContractSerializerOperationBehavior dataContractBehavior =
           op.Behaviors.Find<DataContractSerializerOperationBehavior>()
                as DataContractSerializerOperationBehavior;
        if (dataContractBehavior != null)
        {
            dataContractBehavior.MaxItemsInObjectGraph = 100000;
        }
}
}
serviceHost.Open();

Falls er im Web gehostet wird, müssen Sie eine neue abgeleitete ServiceHost-Klasse erstellen und die Diensthostfactory verwenden, um sie zu laden.

Steuern von Serialisierungseinstellungen in der Konfiguration

MaxItemsInObjectGraph und IgnoreExtensionDataObject können über die Konfiguration mithilfe des dataContractSerializer-Endpunkts oder -Dienstverhaltens gesteuert werden, wie im folgenden Beispiel dargestellt wird.

<configuration>
    <system.serviceModel>
        <behaviors>
            <endpointBehaviors>
                <behavior name="LargeQuotaBehavior">
                    <dataContractSerializer
                      maxItemsInObjectGraph="100000" />
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <client>
            <endpoint address=http://example.com/myservice
                  behaviorConfiguration="LargeQuotaBehavior"
                binding="basicHttpBinding" bindingConfiguration="" 
                            contract="IDataService"
                name="" />
        </client>
    </system.serviceModel>
</configuration>

Serialisierung von gemeinsamen Typen, Objektdiagrammbeibehaltung und benutzerdefinierte Serialisierungsprogramme

Das DataContractSerializer serialisiert mithilfe von Datenvertragsnamen und nicht mithilfe von .NET-Typnamen. Dies entspricht dienstorientierten Architekturgrundsätzen und ermöglicht einen hohen Grad an Flexibilität – die .NET-Typen können sich ändern, ohne sich auf den Übertragungsvertrag auszuwirken. In seltenen Fällen kann es notwendig sein, tatsächliche .NET-Typnamen zu serialisieren und dabei eine enge Verknüpfung zwischen dem Client und dem Server vorzunehmen, ähnlich wie bei der .NET-Framework-Remotetechnologie. Dies wird nicht empfohlen, außer in seltenen Fällen, die normalerweise auftreten, wenn zu WCF von .NET Framework-Remoting migriert wird. In diesem Fall müssen Sie die NetDataContractSerializer-Klasse statt der DataContractSerializer-Klasse verwenden.

Das DataContractSerializer serialisiert normalerweise Objektdiagramme als Objektstrukturen, d. h. auf dasselbe Objekt wird mehr als einmal verwiesen, es wird mehr als einmal serialisiert. Betrachten Sie z. B. eine PurchaseOrder-Instanz, die über zwei Felder vom Typ Adresse mit den Namen billTo und shipTo verfügt. Wenn beide Felder auf dieselbe Adressinstanz festgelegt werden, gibt es zwei identische Adressinstanzen nach der Serialisierung und der Deserialisierung. Dies erfolgt, weil es kein interoperables Standardverfahren zur Darstellung von Objektdiagrammen in XML gibt (außer dem älteren SOAP-Codierungsstandard, der für XmlSerializer verfügbar ist, wie im vorigen Abschnitt über Style und Use beschrieben). Objektdiagramme als Strukturen zu serialisieren, bringt gewisse Nachteile mit sich, z. B. können Diagramme mit Zirkelverweisen nicht serialisiert werden. Gelegentlich ist es erforderlich, auf echte Objektdiagrammserialisierung umzustellen, obwohl sie nicht interoperabel ist. Dies kann durch Verwendung vom DataContractSerializer erfolgen, das mit dem preserveObjectReferences-Parameter konstruiert ist, der auf true festgelegt ist.

Gelegentlich reichen die integrierten Serialisierungsprogramme nicht für das Szenario aus. In den meisten Fällen können Sie trotzdem die XmlObjectSerializer-Abstraktion verwenden, von der sowohl das DataContractSerializer als auch das NetDataContractSerializer abgeleitet werden.

Die drei vorherigen Fälle (.NET-Typbeibehaltung, Objektdiagrammbeibehaltung und die vollkommen benutzerdefinierte XmlObjectSerializer-basierte Serialisierung) erfordern alle die Einbindung eines benutzerdefinierten Serialisierungsprogramms. Dazu führen Sie die folgenden Schritte aus:

  1. Schreiben Sie ein eigenes Verhalten, das sich vom DataContractSerializerOperationBehavior herleitet.
  2. Überschreiben Sie die beiden CreateSerializer-Methoden, um Ihr eigenes Serialisierungsprogramm zurückzugeben (entweder das NetDataContractSerializer, DataContractSerializer mit preserveObjectReferences festgelegt auf true oder Ihr eigenes benutzerdefiniertes XmlObjectSerializer).
  3. Vor dem Öffnen des Diensthosts oder dem Erstellen eines Clientkanals entfernen Sie das vorhandene DataContractSerializerOperationBehavior-Verhalten und binden die benutzerdefinierte abgeleitete Klasse ein, die Sie in den vorherigen Schritten erstellt haben.

Weitere Informationen zu erweiterten Serialisierungskonzepten finden Sie unter Serialisierung und Deserialisierung.

Siehe auch

Aufgaben

Gewusst wie: Aktivieren des Streamingmodus
Gewusst wie: Erstellen eines grundlegenden Datenvertrags für eine Klasse oder Struktur

Konzepte

Verwenden der XmlSerializer-Klasse