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 toCity
och 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
, float
och 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 Style
XmlSerializerFormatAttribute 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 DataContractSerializerOperationBehavior
MaxItemsInObjectGraph
egenskaperna , IgnoreExtensionDataObject
och 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 MaxItemsInObjectGraph
IgnoreExtensionDataObject
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å preserveObjectReferences
true
.
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 XmlObjectSerializer
serialisering) kräver alla att en anpassad serialiserare ansluts. Utför då följande steg:
Skriv ditt eget beteende som härleds från DataContractSerializerOperationBehavior.
Åsidosätt de två
CreateSerializer
metoderna för att returnera din egen serialiserare (antingen NetDataContractSerializer, DataContractSerializer medpreserveObjectReferences
inställd påtrue
eller din egen anpassade XmlObjectSerializer).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.