Dela via


Ange dataöverföring i tjänstkontrakt

Windows Communication Foundation (WCF) kan betraktas som en meddelandeinfrastruktur. Tjänståtgärder kan ta emot meddelanden, bearbeta dem och skicka meddelanden till dem. Meddelanden beskrivs med hjälp av åtgärdskontrakt. Tänk till exempel på följande kontrakt.

[ServiceContract]  
public interface IAirfareQuoteService  
{  
    [OperationContract]  
    float GetAirfare(string fromCity, string toCity);  
}  
<ServiceContract()>  
Public Interface IAirfareQuoteService  
  
    <OperationContract()>  
    Function GetAirfare(fromCity As String, toCity As String) As Double  
End Interface  

Här accepterar åtgärden GetAirfare ett meddelande med information om fromCity och toCityoch returnerar sedan ett meddelande som innehåller ett tal.

I det här avsnittet beskrivs de olika sätt på vilka ett åtgärdskontrakt kan beskriva meddelanden.

Beskriva meddelanden med hjälp av parametrar

Det enklaste sättet att beskriva ett meddelande är att använda en parameterlista och returvärdet. I föregående exempel fromCity användes strängparametrarna och toCity för att beskriva begärandemeddelandet, och returvärdet float användes för att beskriva svarsmeddelandet. Om enbart returvärdet inte räcker för att beskriva ett svarsmeddelande kan utparametrar användas. Följande åtgärd har fromCity till exempel och toCity i sitt begärandemeddelande och ett tal tillsammans med en valuta i sitt svarsmeddelande:

[OperationContract]  
float GetAirfare(string fromCity, string toCity, out string currency);  
<OperationContract()>  
    Function GetAirfare(fromCity As String, toCity As String) As Double  

Dessutom kan du använda referensparametrar för att göra en parameter till en del av både begäran och svarsmeddelandet. Parametrarna måste vara av typer som kan serialiseras (konverteras till XML). Som standard använder WCF en komponent som kallas klassen för att utföra den här konverteringen DataContractSerializer . De flesta primitiva typer (till exempel int, string, floatoch DateTime.) stöds. Användardefinierade typer måste normalt ha ett datakontrakt. Mer information finns i Använda datakontrakt.

public interface IAirfareQuoteService  
{  
    [OperationContract]  
    float GetAirfare(Itinerary itinerary, DateTime date);  
  
    [DataContract]  
    public class Itinerary  
    {  
        [DataMember]  
        public string fromCity;  
        [DataMember]  
        public string toCity;  
   }  
}  
Public Interface IAirfareQuoteService  
    <OperationContract()>  
    GetAirfare(itinerary as Itinerary, date as DateTime) as Double  
  
    <DataContract()>  
    Class Itinerary  
  
        <DataMember()>  
        Public fromCity As String  
        <DataMember()>  
        Public toCity As String  
    End Class  
End Interface  

DataContractSerializer Ibland är inte tillräckligt för att serialisera dina typer. WCF stöder en alternativ serialiseringsmotor, XmlSerializer, som du också kan använda för att serialisera parametrar. Med XmlSerializer kan du använda mer kontroll över den resulterande XML-koden med hjälp av attribut som XmlAttributeAttribute. Om du vill växla till att använda XmlSerializer för en viss åtgärd eller för hela tjänsten använder du XmlSerializerFormatAttribute attributet för en åtgärd eller en tjänst. Till exempel:

[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;  
}  
<ServiceContract()>  
Public Interface IAirfareQuoteService  
    <OperationContract()>  
    <XmlSerializerFormat>  
    GetAirfare(itinerary as Itinerary, date as DateTime) as Double  
  
End Interface  
  
Class Itinerary  
  
    Public fromCity As String  
    Public toCity As String  
    <XmlSerializerFormat()>  
    Public isFirstClass As Boolean  
End Class  

Mer information finns i Använda XmlSerializer-klassen. Kom ihåg att manuellt växla till det XmlSerializer som visas här rekommenderas inte om du inte har specifika skäl att göra det enligt beskrivningen i det ämnet.

Om du vill isolera .NET-parameternamn från kontraktsnamn kan du använda MessageParameterAttribute attributet och använda Name egenskapen för att ange kontraktsnamnet. Till exempel motsvarar följande åtgärdskontrakt det första exemplet i det här avsnittet.

[OperationContract]  
public float GetAirfare(  
    [MessageParameter(Name="fromCity")] string originCity,  
    [MessageParameter(Name="toCity")] string destinationCity);  
<OperationContract()>  
  Function GetAirfare(<MessageParameter(Name := "fromCity")> fromCity As String, <MessageParameter(Name := "toCity")> toCity As String) As Double  

Beskriva tomma meddelanden

Ett tomt meddelande om begäran kan beskrivas genom att inte ha några indata- eller referensparametrar. Till exempel i C#:

[OperationContract]

public int GetCurrentTemperature();

Till exempel i Visual Basic:

<OperationContract()>

Function GetCurrentTemperature() as Integer

Ett tomt svarsmeddelande kan beskrivas genom att ha en void returtyp och inga utdata eller referensparametrar. Till exempel i:

[OperationContract]  
public void SetTemperature(int temperature);  
<OperationContract()>  
Sub SetTemperature(temperature As Integer)  

Detta skiljer sig från en enkelriktad åtgärd, till exempel:

[OperationContract(IsOneWay=true)]  
public void SetLightbulbStatus(bool isOn);  
<OperationContract(IsOneWay:=True)>  
Sub SetLightbulbStatus(isOne As Boolean)  

Åtgärden SetTemperatureStatus returnerar ett tomt meddelande. Det kan returnera ett fel i stället om det uppstår ett problem med att bearbeta indatameddelandet. Åtgärden SetLightbulbStatus returnerar ingenting. Det finns inget sätt att kommunicera ett feltillstånd från den här åtgärden.

Beskriva meddelanden med hjälp av meddelandekontrakt

Du kanske vill använda en enda typ för att representera hela meddelandet. Även om det är möjligt att använda ett datakontrakt för detta ändamål, är det rekommenderade sättet att göra detta att använda ett meddelandekontrakt – detta undviker onödiga nivåer av omslutning i den resulterande XML-koden. Med meddelandekontrakt kan du dessutom utöva mer kontroll över resulterande meddelanden. Du kan till exempel bestämma vilka delar av informationen som ska finnas i meddelandetexten och vilka som ska finnas i meddelanderubrikerna. I följande exempel visas användningen av meddelandekontrakt.

[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;  
}  
<ServiceContract()>  
Public Interface IAirfareQuoteService  
    <OperationContract()>  
    Function GetAirfare(request As GetAirfareRequest) As GetAirfareResponse  
End Interface  
  
<MessageContract()>  
Public Class GetAirfareRequest  
    <MessageHeader()>
    Public Property date as DateTime  
    <MessageBodyMember()>  
    Public Property itinerary As Itinerary  
End Class  
  
<MessageContract()>  
Public Class GetAirfareResponse  
    <MessageBodyMember()>  
    Public Property airfare As Double  
    <MessageBodyMember()> Public Property currency As String  
End Class  
  
<DataContract()>  
Public Class Itinerary  
    <DataMember()> Public Property fromCity As String  
    <DataMember()> Public Property toCity As String  
End Class  

Mer information finns i Använda meddelandekontrakt.

I föregående exempel DataContractSerializer används klassen fortfarande som standard. Klassen XmlSerializer kan också användas med meddelandekontrakt. Det gör du genom att använda XmlSerializerFormatAttribute attributet för antingen åtgärden eller kontraktet och använda typer som är kompatibla med XmlSerializer klassen i meddelandehuvudena och brödtextmedlemmar.

Beskriva meddelanden med hjälp av Flöden

Ett annat sätt att beskriva meddelanden i åtgärder är att använda Stream klassen eller någon av dess härledda klasser i ett åtgärdskontrakt eller som medlem i meddelandekontraktets brödtext (det måste vara den enda medlemmen i det här fallet). För inkommande meddelanden måste typen vara Stream– du kan inte använda härledda klasser.

I stället för att anropa serialiseraren hämtar WCF data från en ström och placerar dem direkt i ett utgående meddelande, eller hämtar data från ett inkommande meddelande och placerar dem direkt i en ström. Följande exempel visar användningen av strömmar.

[OperationContract]  
public Stream DownloadFile(string fileName);  
<OperationContract()>  
Function DownloadFile(fileName As String) As String  

Du kan inte kombinera Stream och inte strömma data i en enda meddelandetext. Använd ett meddelandekontrakt för att placera extra data i meddelandehuvuden. I följande exempel visas felaktig användning av strömmar när du definierar åtgärdskontraktet.

//Incorrect:  
// [OperationContract]  
// public void UploadFile (string fileName, Stream fileData);  
'Incorrect:  
    '<OperationContract()>  
    Public Sub UploadFile(fileName As String, fileData As StreamingContext)  

Följande exempel visar korrekt användning av strömmar när du definierar ett åtgärdskontrakt.

[OperationContract]  
public void UploadFile (UploadFileMessage message);  
//code omitted  
[MessageContract]  
public class UploadFileMessage  
{  
    [MessageHeader] public string fileName;  
    [MessageBodyMember] public Stream fileData;  
}  
<OperationContract()>  
Public Sub UploadFile(fileName As String, fileData As StreamingContext)  
'Code Omitted  
<MessageContract()>  
Public Class UploadFileMessage  
   <MessageHeader()>  
    Public Property fileName As String  
    <MessageBodyMember()>  
    Public Property fileData As Stream  
End Class  

Mer information finns i Stora data och direktuppspelning.

Använda meddelandeklassen

Om du vill ha fullständig programmatisk kontroll över meddelanden som skickas eller tas emot kan du använda Message klassen direkt, som du ser i följande exempelkod.

[OperationContract]  
public void LogMessage(Message m);  
<OperationContract()>  
Sub LogMessage(m As Message)  

Det här är ett avancerat scenario som beskrivs i detalj i Använda meddelandeklassen.

Beskriva felmeddelanden

Förutom de meddelanden som beskrivs av returvärdet och utdata- eller referensparametrarna kan alla åtgärder som inte är enkelriktade returnera minst två möjliga meddelanden: dess normala svarsmeddelande och ett felmeddelande. Överväg följande åtgärdskontrakt.

[OperationContract]  
float GetAirfare(string fromCity, string toCity, DateTime date);  
<OperationContract()>  
Function GetAirfare(fromCity As String, toCity As String, date as DateTime)  

Den här åtgärden kan antingen returnera ett normalt meddelande som innehåller ett float tal eller ett felmeddelande som innehåller en felkod och en beskrivning. Du kan göra detta genom att skapa en FaultException i din tjänstimplementering.

Du kan ange ytterligare möjliga felmeddelanden med hjälp av attributet FaultContractAttribute . De ytterligare felen måste vara serialiserbara med hjälp av DataContractSerializer, som visas i följande exempelkod.

[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;  
}  
<OperationContract()>  
<FaultContract(GetType(ItineraryNotAvailableFault))>  
Function GetAirfare(fromCity As String, toCity As String, date as DateTime) As Double  
  
'Code Omitted  
<DataContract()>  
Public Class  
  <DataMember()>  
  Public Property IsAlternativeDateAvailable As Boolean  
  <DataMember()>  
  Public Property alternativeSuggestedDate As DateTime  
End Class  

Dessa ytterligare fel kan genereras genom att utlösa en FaultException<TDetail> av lämplig datakontraktstyp. Mer information finns i Hantera undantag och fel.

Du kan inte använda XmlSerializer klassen för att beskriva fel. Har XmlSerializerFormatAttribute ingen effekt på felkontrakt.

Använda härledda typer

Du kanske vill använda en bastyp i en åtgärd eller ett meddelandekontrakt och sedan använda en härledd typ när du faktiskt anropar åtgärden. I det här fallet måste du använda attributet ServiceKnownTypeAttribute eller någon annan mekanism för att tillåta användning av härledda typer. Överväg följande åtgärd.

[OperationContract]  
public bool IsLibraryItemAvailable(LibraryItem item);  
<OperationContract()>  
    Function IsLibraryItemAvailable(item As LibraryItem) As Boolean  

Anta att två typer, Book och Magazine, härleds från LibraryItem. Om du vill använda dessa typer i IsLibraryItemAvailable åtgärden kan du ändra åtgärden på följande sätt:

[OperationContract]

[ServiceKnownType(typeof(Book))]

[ServiceKnownType(typeof(Magazine))]

public bool IsLibraryItemAvailable(LibraryItem item);

Du kan också använda KnownTypeAttribute attributet när standardvärdet DataContractSerializer används, som du ser i följande exempelkod.

[OperationContract]  
public bool IsLibraryItemAvailable(LibraryItem item);  
  
// code omitted
  
[DataContract]  
[KnownType(typeof(Book))]  
[KnownType(typeof(Magazine))]  
public class LibraryItem  
{  
    //code omitted  
}  
<OperationContract()>  
Function IsLibraryItemAvailable(item As LibraryItem) As Boolean  
  
'Code Omitted  
<DataContract()>  
<KnownType(GetType(Book))>  
<KnownType(GetType(Magazine))>  
Public Class LibraryItem  
  'Code Omitted  
End Class  

Du kan använda attributet XmlIncludeAttribute när du använder XmlSerializer.

Du kan använda ServiceKnownTypeAttribute attributet för en åtgärd eller för hela tjänsten. Den accepterar antingen en typ eller namnet på den metod som ska anropas för att hämta en lista över kända typer, precis som KnownTypeAttribute attributet. Mer information finns i Kända typer av datakontrakt.

Ange användning och formatmall

När du beskriver tjänster med hjälp av WSDL (Web Services Description Language) är de två vanliga formatmallarna Dokument- och fjärrproceduranrop (RPC). I formatmallen Dokument beskrivs hela meddelandetexten med hjälp av schemat, och WSDL beskriver de olika delarna i meddelandetexten genom att referera till element i schemat. I RPC-formatet refererar WSDL till en schematyp för varje meddelandedel i stället för ett element. I vissa fall måste du välja någon av dessa formatmallar manuellt. Du kan göra detta genom att använda DataContractFormatAttribute attributet och ange Style egenskapen (när den DataContractSerializer används) eller genom att ange StyleXmlSerializerFormatAttribute attributet (när du använder XmlSerializer).

Dessutom har stöd för XmlSerializer två former av serialiserad XML: Literal och Encoded. Literal är det vanligaste godkända formuläret och är det enda formulär som DataContractSerializer stöds. Encoded är ett äldre formulär som beskrivs i avsnitt 5 i SOAP-specifikationen och rekommenderas inte för nya tjänster. Om du vill växla till Encoded läge anger du Use egenskapen för XmlSerializerFormatAttribute attributet till Encoded.

I de flesta fall bör du inte ändra standardinställningarna för Style egenskaperna och Use .

Kontrollera serialiseringsprocessen

Du kan göra ett antal saker för att anpassa hur data serialiseras.

Ändra serverseriellisering Inställningar

När standardvärdet DataContractSerializer används kan du styra vissa aspekter av serialiseringsprocessen för tjänsten genom att tillämpa ServiceBehaviorAttribute attributet på tjänsten. Mer specifikt kan du använda MaxItemsInObjectGraph egenskapen för att ange den kvot som begränsar det maximala antalet objekt DataContractSerializer som deserialiserar. Du kan använda egenskapen IgnoreExtensionDataObject för att inaktivera funktionen för versionshantering av avrundning. Mer information om kvoter finns i Säkerhetsöverväganden för data. Mer information om rund-tripping finns i Framåtkompatibla datakontrakt.

[ServiceBehavior(MaxItemsInObjectGraph=100000)]  
public class MyDataService:IDataService  
{  
    public DataPoint[] GetData()  
    {  
       // Implementation omitted  
    }  
}  
<ServiceBehavior(MaxItemsInObjectGraph:=100000)>  
Public Class MyDataService Implements IDataService  
  
    Function GetData() As DataPoint()  
         ‘ Implementation omitted  
    End Function  
End Interface  

Serialiseringsbeteenden

Två beteenden är tillgängliga i WCF, DataContractSerializerOperationBehavior och XmlSerializerOperationBehavior som automatiskt ansluts beroende på vilken serialiserare som används för en viss åtgärd. Eftersom dessa beteenden tillämpas automatiskt behöver du normalt inte vara medveten om dem.

Har dock DataContractSerializerOperationBehaviorMaxItemsInObjectGraphegenskaperna , IgnoreExtensionDataObjectoch DataContractSurrogate som du kan använda för att anpassa serialiseringsprocessen. De två första egenskaperna har samma betydelse som beskrivs i föregående avsnitt. Du kan använda DataContractSurrogate egenskapen för att aktivera surrogater för datakontrakt, vilket är en kraftfull mekanism för att anpassa och utöka serialiseringsprocessen. Mer information finns i Surrogater för datakontrakt.

Du kan använda DataContractSerializerOperationBehavior för att anpassa både klient- och server serialisering. I följande exempel visas hur du ökar MaxItemsInObjectGraph kvoten för klienten.

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();  
Dim factory As ChannelFactory(Of IDataService) = New ChannelFactory(Of IDataService)(binding, address)  
For Each op As OperationDescription In factory.Endpoint.Contract.Operations  
        Dim dataContractBehavior As DataContractSerializerOperationBehavior = op.Behaviors.Find(Of DataContractSerializerOperationBehavior)()  
        If dataContractBehavior IsNot Nothing Then  
            dataContractBehavior.MaxItemsInObjectGraph = 100000  
        End If  
     Next  
    Dim client As IDataService = factory.CreateChannel  

Följande är motsvarande kod för tjänsten i det lokalt installerade fallet:

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();  
Dim serviceHost As ServiceHost = New ServiceHost(GetType(IDataService))  
        For Each ep As ServiceEndpoint In serviceHost.Description.Endpoints  
            For Each op As OperationDescription In ep.Contract.Operations  
                Dim dataContractBehavior As DataContractSerializerOperationBehavior = op.Behaviors.Find(Of DataContractSerializerOperationBehavior)()  
  
                If dataContractBehavior IsNot Nothing Then  
                    dataContractBehavior.MaxItemsInObjectGraph = 100000  
                End If  
            Next  
        Next  
        serviceHost.Open()  

I det webbaserade fallet måste du skapa en ny ServiceHost härledd klass och använda en tjänstvärdfabrik för att ansluta den.

Kontrollera serialisering Inställningar i konfigurationen

Och MaxItemsInObjectGraphIgnoreExtensionDataObject kan styras via konfigurationen dataContractSerializer med hjälp av slutpunkts- eller tjänstbeteendet, som du ser i följande exempel.

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

Serialisering av delad typ, bevarande av objektdiagram och anpassade serialiserare

Serialiserar DataContractSerializer med hjälp av datakontraktsnamn och inte .NET-typnamn. Detta överensstämmer med tjänstorienterade arkitekturprinciper och ger stor flexibilitet – .NET-typerna kan ändras utan att påverka trådkontraktet. I sällsynta fall kanske du vill serialisera faktiska .NET-typnamn och därmed införa en nära koppling mellan klienten och servern, liknande .NET Framework-fjärrkommunikationstekniken. Detta är inte en rekommenderad metod, förutom i sällsynta fall som vanligtvis inträffar vid migrering till WCF från .NET Framework-fjärrkommunikation. I det här fallet måste du använda NetDataContractSerializer klassen i stället för DataContractSerializer klassen.

Normalt DataContractSerializer serialiserar objektdiagram som objektträd. Om samma objekt refereras till mer än en gång serialiseras det alltså mer än en gång. Tänk dig till exempel en PurchaseOrder instans som har två fält av typen Adress som heter billTo och shipTo. Om båda fälten är inställda på samma adressinstans finns det två identiska adressinstanser efter serialisering och deserialisering. Detta görs eftersom det inte finns något standardkompatibelt sätt att representera objektdiagram i XML (förutom den äldre SOAP-kodade standarden som är tillgänglig på XmlSerializer, enligt beskrivningen i föregående avsnitt på Style och Use). Serialisering av objektdiagram som träd har vissa nackdelar, till exempel kan grafer med cirkelreferenser inte serialiseras. Ibland är det nödvändigt att växla till true object graph serialization, även om det inte är driftskompatibelt. Detta kan göras med hjälp av den DataContractSerializer konstruerade med parametern inställd på preserveObjectReferencestrue.

Ibland räcker inte de inbyggda serialiserarna för ditt scenario. I de flesta fall kan du fortfarande använda abstraktionen XmlObjectSerializer som både DataContractSerializer och härleds NetDataContractSerializer från.

De föregående tre fallen (.NET-typbevarande, bevarande av objektdiagram och helt anpassad XmlObjectSerializerserialisering) kräver alla att en anpassad serialiserare ansluts. Utför då följande steg:

  1. Skriv ditt eget beteende som härleds från DataContractSerializerOperationBehavior.

  2. Åsidosätt de två CreateSerializer metoderna för att returnera din egen serialiserare (antingen NetDataContractSerializer, DataContractSerializer med preserveObjectReferences inställd på trueeller din egen anpassade XmlObjectSerializer).

  3. Innan du öppnar tjänstvärden eller skapar en klientkanal tar du bort det befintliga DataContractSerializerOperationBehavior beteendet och ansluter den anpassade härledda klassen som du skapade i föregående steg.

Mer information om avancerade serialiseringsbegrepp finns i Serialisering och deserialisering.

Se även