Dela via


Översikt över arkitektur för dataöverföring

Windows Communication Foundation (WCF) kan betraktas som en meddelandeinfrastruktur. Den kan ta emot meddelanden, bearbeta dem och skicka dem till användarkod för ytterligare åtgärder, eller så kan den konstruera meddelanden från data som ges av användarkod och leverera dem till ett mål. Det här avsnittet, som är avsett för avancerade utvecklare, beskriver arkitekturen för hantering av meddelanden och inneslutna data. En enklare, uppgiftsorienterad vy över hur du skickar och tar emot data finns i Ange dataöverföring i tjänstkontrakt.

Kommentar

I det här avsnittet beskrivs information om WCF-implementering som inte visas genom att undersöka WCF-objektmodellen. Två försiktighetsord är i ordning när det gäller dokumenterad implementeringsinformation. För det första förenklas beskrivningarna. den faktiska implementeringen kan vara mer komplex på grund av optimeringar eller andra orsaker. För det andra bör du aldrig förlita dig på specifik implementeringsinformation, inte ens dokumenterade, eftersom dessa kan ändras utan föregående meddelande från version till version eller ens i en serviceversion.

Grundläggande arkitektur

Kärnan i WCF-funktionerna för meddelandehantering är Message klassen, som beskrivs i detalj i Använda meddelandeklassen. Körningskomponenterna i WCF kan delas in i två huvuddelar: kanalstacken och tjänstramverket, där Message klassen är anslutningspunkten.

Kanalstacken ansvarar för att konvertera mellan en giltig Message instans och en åtgärd som motsvarar sändning eller mottagning av meddelandedata. På sändningssidan tar kanalstacken en giltig Message instans och utför efter viss bearbetning en åtgärd som logiskt motsvarar att skicka meddelandet. Åtgärden kan vara att skicka TCP- eller HTTP-paket, köa meddelandet i Message Queuing, skriva meddelandet till en databas, spara det till en filresurs eller någon annan åtgärd, beroende på implementeringen. Den vanligaste åtgärden är att skicka meddelandet via ett nätverksprotokoll. På mottagarsidan sker motsatsen – en åtgärd identifieras (som kan vara TCP- eller HTTP-paket som anländer eller någon annan åtgärd), och efter bearbetning konverterar kanalstacken den här åtgärden till en giltig Message instans.

Du kan använda WCF med hjälp Message av klassen och kanalstacken direkt. Det är dock svårt och tidskrävande att göra det. Dessutom Message ger objektet inget metadatastöd, så du kan inte generera starkt skrivna WCF-klienter om du använder WCF på det här sättet.

Därför innehåller WCF ett tjänstramverk som tillhandahåller en lätthanterad programmeringsmodell som du kan använda för att konstruera och ta emot Message objekt. Tjänstramverket mappar tjänster till .NET Framework-typer via begreppet tjänstkontrakt och skickar meddelanden till användaråtgärder som helt enkelt är .NET Framework-metoder som har markerats med OperationContractAttribute attributet (mer information finns i Designa tjänstkontrakt). Dessa metoder kan ha parametrar och returvärden. På tjänstsidan konverterar tjänstramverket inkommande Message instanser till parametrar och konverterar returvärden till utgående Message instanser. På klientsidan gör den motsatsen. Tänk till exempel på åtgärden FindAirfare nedan.

[ServiceContract]
public interface IAirfareFinderService
{
    [OperationContract]
    int FindAirfare(string FromCity, string ToCity, out bool IsDirectFlight);
}
<ServiceContract()> _
Public Interface IAirfareFinderService

    <OperationContract()> _
    Function FindAirfare(ByVal FromCity As String, _
    ByVal ToCity As String, ByRef IsDirectFlight As Boolean) As Integer

End Interface

Anta att FindAirfare anropas på klienten. Tjänstramverket på klienten konverterar parametrarna FromCity och ToCity till en utgående Message instans och skickar den till kanalstacken som ska skickas.

När en Message instans kommer från kanalstacken på tjänstsidan extraherar tjänstramverket relevanta data från meddelandet för att fylla i FromCity parametrarna och ToCity och anropar sedan metoden på tjänstsidan FindAirfare . När metoden returneras tar tjänstramverket det returnerade heltalsvärdet och IsDirectFlight utdataparametern och skapar en Message objektinstans som innehåller den här informationen. Den skickar sedan instansen Message till kanalstacken som ska skickas tillbaka till klienten.

På klientsidan visas en Message instans som innehåller svarsmeddelandet från kanalstacken. Tjänstramverket extraherar returvärdet och IsDirectFlight värdet och returnerar dessa till klientens anropare.

Meddelandeklass

Klassen Message är avsedd att vara en abstrakt representation av ett meddelande, men dess design är starkt kopplad till SOAP-meddelandet. A Message innehåller tre viktiga informationsdelar: en meddelandetext, meddelandehuvuden och meddelandeegenskaper.

Meddelandetext

Meddelandetexten är avsedd att representera meddelandets faktiska datanyttolast. Meddelandetexten representeras alltid som en XML-informationsuppsättning. Det innebär inte att alla meddelanden som skapas eller tas emot i WCF måste vara i XML-format. Det är upp till kanalstacken att bestämma hur meddelandetexten ska tolkas. Det kan generera det som XML, konvertera det till något annat format eller till och med utelämna det helt. Med de flesta bindningar som WCF tillhandahåller representeras meddelandetexten naturligtvis som XML-innehåll i brödtextavsnittet i ett SOAP-kuvert.

Det är viktigt att inse att Message klassen inte nödvändigtvis innehåller en buffert med XML-data som representerar brödtexten. Logiskt Message sett innehåller en XML-informationsuppsättning, men den här infouppsättningen kan vara dynamiskt konstruerad och kanske aldrig finns fysiskt i minnet.

Placera data i meddelandetexten

Det finns ingen enhetlig mekanism för att placera data i en meddelandetext. Klassen Message har en abstrakt metod, OnWriteBodyContents(XmlDictionaryWriter), som tar en XmlDictionaryWriter. Varje underklass i Message klassen ansvarar för att åsidosätta den här metoden och skriva ut sitt eget innehåll. Meddelandetexten innehåller logiskt den XML-informationsuppsättning som OnWriteBodyContent skapas. Tänk till exempel på följande Message underklass.

public class AirfareRequestMessage : Message
{
    public string fromCity = "Tokyo";
    public string toCity = "London";
    //code omitted…
    protected override void OnWriteBodyContents(XmlDictionaryWriter w)
    {
        w.WriteStartElement("airfareRequest");
        w.WriteElementString("from", fromCity);
        w.WriteElementString("to", toCity);
        w.WriteEndElement();
    }

    public override MessageVersion Version
    {
        get { throw new NotImplementedException("The method is not implemented.") ; }
    }

    public override MessageProperties Properties
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }
    public override MessageHeaders Headers
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override bool IsEmpty
    {
        get
        {
            return base.IsEmpty;
        }
    }

    public override bool IsFault
    {
        get
        {
            return base.IsFault;
        }
    }
}
Public Class AirfareRequestMessage
    Inherits Message

    Public fromCity As String = "Tokyo"
    Public toCity As String = "London"
    ' Code omitted…
    Protected Overrides Sub OnWriteBodyContents(ByVal w As XmlDictionaryWriter)
        w.WriteStartElement("airfareRequest")
        w.WriteElementString("from", fromCity)
        w.WriteElementString("to", toCity)
        w.WriteEndElement()
    End Sub

    Public Overrides ReadOnly Property Version() As MessageVersion
        Get
            Throw New NotImplementedException("The method is not implemented.")
        End Get
    End Property

    Public Overrides ReadOnly Property Properties() As MessageProperties
        Get
            Throw New Exception("The method or operation is not implemented.")
        End Get
    End Property

    Public Overrides ReadOnly Property Headers() As MessageHeaders
        Get
            Throw New Exception("The method or operation is not implemented.")
        End Get
    End Property


    Public Overrides ReadOnly Property IsEmpty() As Boolean
        Get
            Return MyBase.IsEmpty
        End Get
    End Property

    Public Overrides ReadOnly Property IsFault() As Boolean
        Get
            Return MyBase.IsFault
        End Get
    End Property
End Class

Fysiskt innehåller en AirfareRequestMessage instans endast två strängar ("fromCity" och "toCity"). Meddelandet innehåller dock logiskt följande XML-informationsuppsättning:

<airfareRequest>  
    <from>Tokyo</from>  
    <to>London</to>  
</airfareRequest>  

Naturligtvis skulle du normalt inte skapa meddelanden på det här sättet, eftersom du kan använda tjänstramverket för att skapa ett meddelande som det föregående från parametrarna för åtgärdskontraktet. Dessutom Message har klassen statiska CreateMessage metoder som du kan använda för att skapa meddelanden med vanliga typer av innehåll: ett tomt meddelande, ett meddelande som innehåller ett objekt som serialiserats till XML med DataContractSerializer, ett meddelande som innehåller ett SOAP-fel, ett meddelande som innehåller XML som representeras av en XmlReader, och så vidare.

Hämta data från en meddelandetext

Du kan extrahera data som lagras i en meddelandetext på två huvudsakliga sätt:

  • Du kan hämta hela meddelandetexten samtidigt genom att anropa WriteBodyContents(XmlDictionaryWriter) metoden och skicka in en XML-skrivare. Hela meddelandetexten skrivs ut till den här skrivaren. Att hämta hela meddelandetexten samtidigt kallas också för att skriva ett meddelande. Skrivning görs främst av kanalstacken när meddelanden skickas. En del av kanalstacken får vanligtvis åtkomst till hela meddelandetexten, kodar den och skickar den.

  • Ett annat sätt att få ut information ur meddelandetexten är att anropa GetReaderAtBodyContents() och hämta en XML-läsare. Meddelandetexten kan sedan nås sekventiellt efter behov genom att anropa metoder på läsaren. Att hämta meddelandetexten bit för bit kallas också för att läsa ett meddelande. Läsning av meddelandet används främst av tjänstramverket när meddelanden tas emot. När DataContractSerializer tjänsten till exempel används får tjänstramverket en XML-läsare över brödtexten och skickar den till deserialiseringsmotorn, som sedan börjar läsa meddelandeelementet per element och konstruera motsvarande objektdiagram.

En meddelandetext kan bara hämtas en gång. Detta gör det möjligt att arbeta med strömmar endast framåt. Du kan till exempel skriva en OnWriteBodyContents(XmlDictionaryWriter) åsidosättning som läser från en FileStream och returnerar resultatet som en XML-informationsuppsättning. Du behöver aldrig "spola tillbaka" till början av filen.

Metoderna WriteBodyContents och GetReaderAtBodyContents kontrollerar helt enkelt att meddelandetexten aldrig har hämtats tidigare och anropar OnWriteBodyContentsOnGetReaderAtBodyContentseller , respektive.

Meddelandeanvändning i WCF

De flesta meddelanden kan klassificeras som antingen utgående (de som skapas av tjänstramverket som ska skickas av kanalstacken) eller inkommande (de som kommer från kanalstacken och tolkas av tjänstramverket). Dessutom kan kanalstacken fungera i buffrat läge eller strömningsläge. Tjänstramverket kan också exponera en strömmad eller icke-strömmad programmeringsmodell. Detta leder till de fall som anges i följande tabell, tillsammans med förenklad information om implementeringen.

Meddelandetyp Brödtextdata i meddelandet Implementering av Write (OnWriteBodyContents) Implementering av Read (OnGetReaderAtBodyContents)
Utgående, skapad från en icke-överordnad programmeringsmodell De data som behövs för att skriva meddelandet (till exempel ett objekt och den DataContractSerializer instans som behövs för att serialisera det)* Anpassad logik för att skriva ut meddelandet baserat på lagrade data (till exempel anropa WriteObjectDataContractSerializer om det är serialiseraren som används)* Anropa OnWriteBodyContents, buffra resultatet, returnera en XML-läsare över bufferten
Utgående, skapad från strömmad programmeringsmodell Med Stream de data som ska skrivas* Skriva ut data från den lagrade dataströmmen med hjälp av mekanismen IStreamProvider * Anropa OnWriteBodyContents, buffra resultatet, returnera en XML-läsare över bufferten
Inkommande från strömmande kanalstack Ett Stream objekt som representerar data som kommer in via nätverket med en XmlReader över Skriv ut innehållet från den lagrade XmlReader med hjälp av WriteNode Returnerar den lagrade XmlReader
Inkommande från icke-överordnade kanalstackar En buffert som innehåller brödtextdata med en XmlReader över Skriver ut innehållet från den lagrade XmlReader med hjälp av WriteNode Returnerar det lagrade språket

* Dessa objekt implementeras inte direkt i Message underklasser, utan i klassens BodyWriter underklasser. Mer information om finns i BodyWriterAnvända meddelandeklassen.

Meddelandehuvuden

Ett meddelande kan innehålla rubriker. En rubrik består logiskt av en XML-informationsuppsättning som är associerad med ett namn, ett namnområde och några andra egenskaper. Meddelandehuvuden nås med hjälp av egenskapen på HeadersMessage. Varje rubrik representeras av en MessageHeader klass. Normalt mappas meddelandehuvuden till SOAP-meddelandehuvuden när du använder en kanalstack som konfigurerats för att fungera med SOAP-meddelanden.

Att placera information i ett meddelandehuvud och extrahera information från det liknar att använda meddelandetexten. Processen är något förenklad eftersom strömning inte stöds. Det är möjligt att komma åt innehållet i samma rubrik mer än en gång, och huvuden kan nås i godtycklig ordning, vilket tvingar rubriker att alltid buffras. Det finns ingen generell mekanism för att hämta en XML-läsare över ett huvud, men det finns en MessageHeader intern underklass för WCF som representerar en läsbar rubrik med en sådan funktion. Den här typen av MessageHeader skapas av kanalstacken när ett meddelande med anpassade programhuvuden kommer in. På så sätt kan tjänstramverket använda en deserialiseringsmotor, till exempel DataContractSerializer, för att tolka dessa rubriker.

Mer information finns i Använda meddelandeklassen.

Meddelandeegenskaper

Ett meddelande kan innehålla egenskaper. En egenskap är ett .NET Framework-objekt som är associerat med ett strängnamn. Egenskaper nås via egenskapen på PropertiesMessage.

Till skillnad från meddelandetexten och meddelanderubrikerna (som normalt mappas till SOAP-brödtexten respektive SOAP-huvudena) skickas eller tas meddelandeegenskaper normalt inte emot tillsammans med meddelandena. Meddelandeegenskaper finns främst som en kommunikationsmekanism för att skicka data om meddelandet mellan de olika kanalerna i kanalstacken och mellan kanalstacken och tjänstmodellen.

Http-transportkanalen som ingår i WCF kan till exempel producera olika HTTP-statuskoder, till exempel "404 (hittades inte)" och "500 (internt serverfel)," när den skickar svar till klienter. Innan du skickar ett svarsmeddelande kontrollerar den om Properties innehåller Message en egenskap med namnet "httpResponse" som innehåller ett objekt av typen HttpResponseMessageProperty. Om en sådan egenskap hittas tittar den StatusCode på egenskapen och använder den statuskoden. Om den inte hittas används standardkoden "200 (OK)."

Mer information finns i Använda meddelandeklassen.

Meddelandet som en Vem le

Hittills har vi diskuterat metoder för att komma åt de olika delarna av meddelandet isolerat. Klassen innehåller dock Message även metoder för att arbeta med hela meddelandet som helhet. Metoden skriver till exempel WriteMessage ut hela meddelandet till en XML-skrivare.

För att detta ska vara möjligt måste en mappning definieras mellan hela Message instansen och en XML-informationsuppsättning. En sådan mappning finns faktiskt: WCF använder SOAP-standarden för att definiera den här mappningen. När en Message instans skrivs ut som en XML-informationsuppsättning är den resulterande infouppsättningen det giltiga SOAP-kuvertet som innehåller meddelandet. WriteMessage På så sätt skulle normalt utföra följande steg:

  1. Skriv öppningstaggen för SOAP-kuvertelementet.

  2. Skriv öppningstaggen för SOAP-huvudelementet, skriv ut alla rubriker och stäng rubrikelementet.

  3. Skriv soap-brödtextelementets öppningstagg.

  4. Anropa WriteBodyContents eller en motsvarande metod för att skriva ut brödtexten.

  5. Stäng brödtexten och kuvertelementen.

Föregående steg är nära knutna till SOAP-standarden. Detta kompliceras av det faktum att det finns flera versioner av SOAP, till exempel är det omöjligt att skriva ut SOAP-kuvertelementet korrekt utan att känna till SOAP-versionen som används. I vissa fall kan det också vara önskvärt att stänga av den här komplexa SOAP-specifika mappningen helt.

För dessa ändamål tillhandahålls en Version egenskap på Message. Det kan ställas in på SOAP-versionen som ska användas när du skriver ut meddelandet, eller så kan det ställas in på för att None förhindra SOAP-specifika mappningar. Om egenskapen Version är inställd på None, fungerar metoder som fungerar med hela meddelandet som om meddelandet bara bestod av dess brödtext, till exempel bara WriteMessage anropa WriteBodyContents i stället för att utföra de flera steg som anges ovan. Det förväntas att på inkommande meddelanden Version identifieras automatiskt och anges korrekt.

Kanalstacken

Kanaler

Som tidigare nämnts ansvarar kanalstacken för att konvertera utgående Message instanser till en viss åtgärd (till exempel att skicka paket via nätverket) eller konvertera en åtgärd (till exempel att ta emot nätverkspaket) till inkommande Message instanser.

Kanalstacken består av en eller flera kanaler ordnade i en sekvens. En utgående Message instans skickas till den första kanalen i stacken (kallas även den översta kanalen), som skickar den till nästa kanal nedåt i stacken och så vidare. Meddelandet avslutas i den sista kanalen, som kallas transportkanalen. Inkommande meddelanden kommer från transportkanalen och skickas från kanalen för att kanalisera upp stacken. Från den översta kanalen skickas meddelandet vanligtvis till tjänstramverket. Även om detta är det vanliga mönstret för programmeddelanden kan vissa kanaler fungera något annorlunda, till exempel kan de skicka sina egna infrastrukturmeddelanden utan att skickas ett meddelande från en kanal ovan.

Kanaler kan fungera på meddelandet på olika sätt när det passerar genom stacken. Den vanligaste åtgärden är att lägga till en rubrik i ett utgående meddelande och läsa rubriker i ett inkommande meddelande. En kanal kan till exempel beräkna den digitala signaturen för ett meddelande och lägga till den som en rubrik. En kanal kan också inspektera den här digitala signaturrubriken på inkommande meddelanden och blockera meddelanden som inte har en giltig signatur från att ta sig upp i kanalstacken. Kanaler ställer också ofta in eller inspekterar meddelandeegenskaper. Meddelandetexten ändras vanligtvis inte, men detta är tillåtet, till exempel kan WCF-säkerhetskanalen kryptera meddelandetexten.

Transportkanaler och meddelandekodare

Den längst nedersta kanalen i stacken ansvarar för att faktiskt omvandla en utgående Message, som ändrats av andra kanaler, till en viss åtgärd. På mottagarsidan är det här kanalen som konverterar en åtgärd till en Message annan kanalprocess.

Som tidigare nämnts kan åtgärderna variera: skicka eller ta emot nätverkspaket via olika protokoll, läsa eller skriva meddelandet i en databas, eller köa eller dequeuera meddelandet i en Meddelandekö, för att bara tillhandahålla några exempel. Alla dessa åtgärder har en sak gemensamt: de kräver en transformering mellan WCF-instansenMessage och en faktisk grupp byte som kan skickas, tas emot, läsas, skrivas, köas eller dequeued. Processen att konvertera en Message till en grupp byte kallas kodning, och den omvända processen att skapa en Message från en grupp byte kallas avkodning.

De flesta transportkanaler använder komponenter som kallas meddelandekodare för att utföra kodnings- och avkodningsarbetet. En meddelandekodare är en underklass av MessageEncoder klassen. MessageEncoder innehåller olika ReadMessage och WriteMessage metodöverbelastningar för att konvertera mellan Message och grupper av byte.

På den sändande sidan skickar Message en buffrande transportkanal det objekt som den tog emot från en kanal ovanför den till WriteMessage. Den hämtar tillbaka en matris med byte som den sedan använder för att utföra åtgärden (till exempel paketera dessa byte som giltiga TCP-paket och skicka dem till rätt mål). En direktuppspelningstransportkanal skapar först en Stream (till exempel över den utgående TCP-anslutningen) och skickar sedan både Stream och som den Message behöver skicka till lämplig WriteMessage överlagring, vilket skriver ut meddelandet.

På mottagarsidan extraherar en buffringstransportkanal inkommande byte (till exempel från inkommande TCP-paket) till en matris och anropar ReadMessage för att hämta ett Message objekt som det kan skicka längre upp i kanalstacken. En strömmande transportkanal skapar ett Stream objekt (till exempel en nätverksström över den inkommande TCP-anslutningen) och skickar det till för att ReadMessage hämta tillbaka ett Message objekt.

Separationen mellan transportkanalerna och meddelandekodaren är inte obligatorisk. det är möjligt att skriva en transportkanal som inte använder en meddelandekodare. Fördelen med denna separation är dock enkel sammansättning. Så länge en transportkanal endast använder basen MessageEncoderkan den fungera med valfri WCF- eller tredjepartsmeddelandekodare. På samma sätt kan samma kodare normalt användas i alla transportkanaler.

Meddelandekodaråtgärd

För att beskriva den typiska åtgärden för en kodare är det användbart att överväga följande fyra fall.

Åtgärd Kommentar
Kodning, buffrad I buffrat läge skapar kodaren normalt en buffert med variabel storlek och skapar sedan en XML-skrivare över den. Sedan anropas WriteMessage(XmlWriter) meddelandet som kodas, vilket skriver ut rubrikerna och sedan brödtexten med hjälp av WriteBodyContents(XmlDictionaryWriter), enligt beskrivningen i föregående avsnitt i Message det här avsnittet. Innehållet i bufferten (representeras som en matris med byte) returneras sedan för att transportkanalen ska användas.
Kodning, strömmad I strömmat läge liknar åtgärden ovanstående, men enklare. Det finns inget behov av en buffert. En XML-skrivare skapas normalt över strömmen och WriteMessage(XmlWriter) anropas Message för att skriva ut den till den här skrivaren.
Avkodning, buffrad Vid avkodning i buffrat läge skapas normalt en särskild Message underklass som innehåller buffrade data. Sidhuvudena i meddelandet läss och en XML-läsare som placeras i meddelandetexten skapas. Det här är läsaren som returneras med GetReaderAtBodyContents().
Avkodning, strömmad Vid avkodning i strömmat läge skapas normalt en särskild underklass för meddelande. Strömmen är tillräckligt avancerad för att läsa alla rubriker och placera den på meddelandetexten. En XML-läsare skapas sedan över strömmen. Det här är läsaren som returneras med GetReaderAtBodyContents().

Kodare kan också utföra andra funktioner. Kodarna kan till exempel poola XML-läsare och skrivare. Det är dyrt att skapa en ny XML-läsare eller skrivare varje gång en behövs. Därför upprätthåller kodare normalt en pool av läsare och en pool av författare av konfigurerbar storlek. I beskrivningarna av kodaråtgärden som beskrevs tidigare, när frasen "skapa en XML-läsare/skrivare" används, betyder det normalt att "ta en från poolen eller skapa en om en inte är tillgänglig". Kodaren (och de underklasser som Message skapas under avkodningen) innehåller logik för att returnera läsare och författare till poolerna när de inte längre behövs (till exempel när Message är stängd).

WCF tillhandahåller tre meddelandekodare, även om det är möjligt att skapa ytterligare anpassade typer. De angivna typerna är MTOM (Text, Binary och Message Transmission Optimization Mechanism). Dessa beskrivs i detalj i Välja en meddelandekodare.

IStreamProvider-gränssnittet

När du skriver ett utgående meddelande som innehåller en strömmad brödtext till en XML-skrivare använder Message en sekvens med anrop som liknar följande i implementeringen OnWriteBodyContents(XmlDictionaryWriter) :

  • Skriv nödvändig information före strömmen (till exempel den inledande XML-taggen).

  • Skriv strömmen.

  • Skriv all information som följer strömmen (till exempel den avslutande XML-taggen).

Detta fungerar bra med kodningar som liknar den textbaserade XML-kodningen. Vissa kodningar placerar dock inte XML Infoset-information (till exempel taggar för att starta och avsluta XML-element) tillsammans med data som finns i element. I MTOM-kodningen delas meddelandet till exempel upp i flera delar. En del innehåller XML-informationsuppsättningen, som kan innehålla referenser till andra delar för faktiskt elementinnehåll. XML-informationsuppsättningen är normalt liten jämfört med det strömmade innehållet, så det är klokt att buffrar infouppsättningen, skriver ut den och sedan skriver innehållet på ett strömmat sätt. Det innebär att när taggen för avslutande element skrivs bör strömmen inte ha skrivits ut ännu.

För detta ändamål IStreamProvider används gränssnittet. Gränssnittet har en GetStream() metod som returnerar dataströmmen som ska skrivas. Det rätta sättet att skriva ut en strömmad meddelandetext i OnWriteBodyContents(XmlDictionaryWriter) är följande:

  1. Skriv nödvändig information före strömmen (till exempel den inledande XML-taggen).

  2. Anropa överbelastningen WriteValueXmlDictionaryWriter på som tar en IStreamProvider, med en IStreamProvider implementering som returnerar dataströmmen som ska skrivas.

  3. Skriv all information som följer strömmen (till exempel den avslutande XML-taggen).

Med den här metoden har XML-skrivaren ett val av när du ska anropa GetStream() och skriva ut strömmade data. Till exempel anropar text- och binär XML-skrivarna det omedelbart och skriver ut det strömmade innehållet mellan start- och sluttaggar. MTOM-skrivaren kan välja att anropa GetStream() senare när den är redo att skriva rätt del av meddelandet.

Representerar data i Service Framework

Som anges i avsnittet "Grundläggande arkitektur" i det här avsnittet är tjänstramverket den del av WCF som bland annat ansvarar för att konvertera mellan en användarvänlig programmeringsmodell för meddelandedata och faktiska Message instanser. Normalt representeras ett meddelandeutbyte i tjänstramverket som en .NET Framework-metod markerad med OperationContractAttribute attributet. Metoden kan ta in vissa parametrar och returnera ett returvärde eller utparametrar (eller båda). På tjänstsidan representerar indataparametrarna det inkommande meddelandet, och returvärdet och utdataparametrarna representerar det utgående meddelandet. På klientsidan är det omvända sant. Programmeringsmodellen för att beskriva meddelanden med hjälp av parametrar och returvärdet beskrivs i detalj i Ange dataöverföring i tjänstkontrakt. Det här avsnittet ger dock en kort översikt.

Programmeringsmodeller

WCF-tjänstramverket stöder fem olika programmeringsmodeller för att beskriva meddelanden:

1. Det tomma meddelandet

Det här är det enklaste fallet. Om du vill beskriva ett tomt inkommande meddelande ska du inte använda några indataparametrar.

[OperationContract]
int GetCurrentTemperature();
<OperationContract()> Function GetCurrentTemperature() As Integer

Om du vill beskriva ett tomt utgående meddelande använder du ett returvärde för void och använder inga utgående parametrar:

[OperationContract]
void SetDesiredTemperature(int t);
<OperationContract()> Sub SetDesiredTemperature(ByVal t As Integer)

Observera att detta skiljer sig från ett enkelriktad åtgärdskontrakt:

[OperationContract(IsOneWay = true)]
void SetLightbulb(bool isOn);
<OperationContract(IsOneWay:=True)> Sub SetLightbulb(ByVal isOn As Boolean)

I exemplet SetDesiredTemperature beskrivs ett dubbelriktad meddelandeutbytesmönster. Ett meddelande returneras från åtgärden, men det är tomt. Det går att returnera ett fel från åtgärden. I exemplet "Ange glödlampa" är mönstret för meddelandeutbyte enkelriktat, så det finns inget utgående meddelande att beskriva. Tjänsten kan inte kommunicera någon status tillbaka till klienten i det här fallet.

2. Använda meddelandeklassen direkt

Det är möjligt att använda Message klassen (eller en av dess underklasser) direkt i ett åtgärdskontrakt. I det här fallet skickar Message tjänstramverket bara från åtgärden till kanalstacken och vice versa, utan ytterligare bearbetning.

Det finns två huvudsakliga användningsfall för direkt användning Message . Du kan använda detta för avancerade scenarier när ingen av de andra programmeringsmodellerna ger dig tillräckligt med flexibilitet för att beskriva ditt meddelande. Du kanske till exempel vill använda filer på disken för att beskriva ett meddelande, där filens egenskaper blir meddelandehuvuden och filens innehåll blir meddelandetexten. Du kan sedan skapa något som liknar följande.

public class FileMessage : Message
// Code not shown.
Public Class FileMessage
    Inherits Message
    ' Code not shown.

Den andra vanliga användningen för Message i ett åtgärdskontrakt är när en tjänst inte bryr sig om det specifika meddelandeinnehållet och agerar på meddelandet som på en svart ruta. Du kan till exempel ha en tjänst som vidarebefordrar meddelanden till flera andra mottagare. Kontraktet kan skrivas på följande sätt.

[OperationContract]
public FileMessage GetFile()
{
    //code omitted…
    FileMessage fm = new FileMessage("myFile.xml");
    return fm;
}
<OperationContract()> Public Function GetFile() As FileMessage
    'code omitted…
    Dim fm As New FileMessage("myFile.xml")
    Return fm
End Function

Raden Action="*" inaktiverar effektivt meddelandesändningen och ser till att alla meddelanden som skickas till IForwardingService kontraktet tar sig till åtgärden ForwardMessage . (Normalt skulle avsändaren undersöka meddelandets "Åtgärd"-huvud för att avgöra vilken åtgärd det är avsett för. Action="*" betyder "alla möjliga värden i åtgärdshuvudet".) Kombinationen av Action="*" och att använda Meddelande som en parameter kallas "universellt kontrakt" eftersom det kan ta emot alla möjliga meddelanden. Om du vill kunna skicka alla möjliga meddelanden använder du Meddelande som returvärde och anger ReplyAction till "*". Detta förhindrar att tjänstramverket lägger till ett eget åtgärdshuvud så att du kan styra det här huvudet med hjälp av det Message objekt som du returnerar.

3. Meddelandekontrakt

WCF tillhandahåller en deklarativ programmeringsmodell för att beskriva meddelanden, som kallas meddelandekontrakt. Den här modellen beskrivs i detalj i Använda meddelandekontrakt. I princip representeras hela meddelandet av en enda .NET Framework-typ som använder attribut som MessageBodyMemberAttribute och MessageHeaderAttribute för att beskriva vilka delar av meddelandekontraktsklassen som ska mappas till vilken del av meddelandet.

Meddelandekontrakt ger mycket kontroll över de resulterande Message instanserna (även om det uppenbarligen inte är lika mycket kontroll som att använda Message klassen direkt). Till exempel består meddelandeorgan ofta av flera informationsdelar, som var och en representeras av ett eget XML-element. Dessa element kan antingen förekomma direkt i brödtexten (utan läge) eller omslutas i ett omfattande XML-element. Med hjälp av programmeringsmodellen för meddelandekontrakt kan du fatta ett beslut utan omslut och styra namnet på omslutningsnamnet och namnområdet.

Följande kodexempel på ett meddelandekontrakt visar dessa funktioner.

[MessageContract(IsWrapped = true, WrapperName = "Order")]
public class SubmitOrderMessage
{
    [MessageHeader]
    public string customerID;
    [MessageBodyMember]
    public string item;
    [MessageBodyMember]
    public int quantity;
}
<MessageContract(IsWrapped:=True, WrapperName:="Order")> _
Public Class SubmitOrderMessage
    <MessageHeader()> Public customerID As String
    <MessageBodyMember()> Public item As String
    <MessageBodyMember()> Public quantity As Integer
End Class

Objekt som har markerats som serialiserade (med MessageBodyMemberAttribute, MessageHeaderAttributeeller andra relaterade attribut) måste vara serialiserbara för att kunna delta i ett meddelandekontrakt. Mer information finns i avsnittet "Serialisering" senare i det här avsnittet.

4. Parametrar

En utvecklare som vill beskriva en åtgärd som agerar på flera datadelar behöver ofta inte den kontrollgrad som meddelandekontrakt ger. När du till exempel skapar nya tjänster vill man vanligtvis inte fatta det icke-omslutna beslutet och bestämma namnet på wrapper-elementet. Att fatta dessa beslut kräver ofta djup kunskap om webbtjänster och SOAP.

WCF-tjänstramverket kan automatiskt välja den bästa och mest driftskompatibla SOAP-representationen för att skicka eller ta emot flera relaterade uppgifter, utan att tvinga dessa val på användaren. Detta görs genom att helt enkelt beskriva dessa informationsdelar som parametrar eller returnera värden för ett åtgärdskontrakt. Tänk till exempel på följande åtgärdskontrakt.

[OperationContract]
void SubmitOrder(string customerID, string item, int quantity);
<OperationContract()> _
Sub SubmitOrder( _
    ByVal customerID As String, _
    ByVal item As String, _
    ByVal quantity As Integer)

Tjänstramverket bestämmer sig automatiskt för att placera alla tre informationsdelarna (customerID, itemoch quantity) i meddelandetexten och omsluta dem i ett wrapper-element med namnet SubmitOrderRequest.

Att beskriva den information som ska skickas eller tas emot som en enkel lista över åtgärdskontraktsparametrar är den rekommenderade metoden, såvida det inte finns särskilda skäl att gå över till de mer komplexa meddelandekontrakts- eller Message-baserade programmeringsmodellerna.

5. Stream

Att använda Stream eller en av dess underklasser i ett åtgärdskontrakt eller som en enda meddelandetextdel i ett meddelandekontrakt kan betraktas som en separat programmeringsmodell än den som beskrivs ovan. Att använda Stream på detta sätt är det enda sättet att garantera att ditt kontrakt kan användas på ett strömmat sätt, förutom att skriva din egen streamingkompatibla Message underklass. Mer information finns i Stora data och direktuppspelning.

När Stream eller någon av dess underklasser används på det här sättet anropas inte serialiseraren. För utgående meddelanden skapas en särskild underklass för direktuppspelning Message och strömmen skrivs ut enligt beskrivningen i avsnittet i IStreamProvider gränssnittet. För inkommande meddelanden skapar tjänstramverket en Stream underklass över det inkommande meddelandet och tillhandahåller det till åtgärden.

Begränsningar för programmeringsmodell

De programmeringsmodeller som beskrivs ovan kan inte kombineras godtyckligt. Om en åtgärd till exempel accepterar en meddelandekontraktstyp måste meddelandekontraktet vara dess enda indataparameter. Dessutom måste åtgärden antingen returnera ett tomt meddelande (returtyp av void) eller ett annat meddelandekontrakt. De här begränsningarna för programmeringsmodellen beskrivs i avsnitten för varje specifik programmeringsmodell: Använda meddelandekontrakt, använda meddelandeklassen och stora data och strömning.

Meddelandeformaterare

De programmeringsmodeller som beskrivs ovan stöds genom att ansluta komponenter som kallas meddelandeformaterare till tjänstramverket. Meddelandeformaterare är typer som implementerar IClientMessageFormatter eller IDispatchMessageFormatter gränssnittet, eller båda, för användning i klienter respektive tjänst-WCF-klienter.

Meddelandeformaterare är normalt anslutna av beteenden. Till exempel DataContractSerializerOperationBehavior pluggar i datakontraktets meddelandeformaterare. Detta görs på tjänstsidan genom att ange Formatter rätt formatering i ApplyDispatchBehavior(OperationDescription, DispatchOperation) metoden eller på klientsidan genom att ange Formatter rätt formatering i ApplyClientBehavior(OperationDescription, ClientOperation) metoden.

I följande tabeller visas de metoder som en meddelandeformaterare kan implementera.

Gränssnitt Metod Åtgärd
IDispatchMessageFormatter DeserializeRequest(Message, Object[]) Konverterar en inkommande Message till åtgärdsparametrar
IDispatchMessageFormatter SerializeReply(MessageVersion, Object[], Object) Skapar en utgående Message från åtgärdsreturvärde/ut-parametrar
IClientMessageFormatter SerializeRequest(MessageVersion, Object[]) Skapar en utgående Message från åtgärdsparametrar
IClientMessageFormatter DeserializeReply(Message, Object[]) Konverterar en inkommande Message till ett returvärde/ut-parametrar

Serialization

När du använder meddelandekontrakt eller parametrar för att beskriva meddelandeinnehållet måste du använda serialisering för att konvertera mellan .NET Framework-typer och XML Infoset-representation. Serialisering används på andra platser i WCF, Message till exempel har en generisk GetBody metod som du kan använda för att läsa hela brödtexten i meddelandet som deserialiserats till ett objekt.

WCF stöder två serialiseringstekniker "out of the box" för serialisering och deserialisering av parametrar och meddelandedelar: DataContractSerializer och XmlSerializer. Dessutom kan du skriva anpassade serialiserare. Andra delar av WCF (till exempel generic-metoden GetBody eller SOAP-felserialisering) kan dock begränsas till att endast XmlObjectSerializer använda underklasserna (DataContractSerializer och NetDataContractSerializer, men inte XmlSerializer), eller till och med hårdkodas för att endast DataContractSerializeranvända .

XmlSerializer är serialiseringsmotorn som används i ASP.NET webbtjänster. DataContractSerializer är den nya serialiseringsmotorn som förstår den nya programmeringsmodellen för datakontrakt. DataContractSerializer är standardvalet och valet att använda XmlSerializer kan göras per åtgärd med hjälp av DataContractFormatAttribute attributet.

DataContractSerializerOperationBehavior och XmlSerializerOperationBehavior är de åtgärdsbeteenden som ansvarar för att ansluta till meddelandeformatrarna för DataContractSerializer respektive XmlSerializer. Beteendet DataContractSerializerOperationBehavior kan faktiskt fungera med alla serialiserare som härleds från XmlObjectSerializer, inklusive NetDataContractSerializer (beskrivs i detalj i Använda fristående serialisering). Beteendet anropar en av de CreateSerializer virtuella metodöverlagringarna för att hämta serialiseraren. Om du vill ansluta en annan serialiserare skapar du en ny DataContractSerializerOperationBehavior underklass och åsidosätter båda CreateSerializer överlagringarna.

Se även